1use std::{
2 fs::{self, File},
3 io::{BufReader, Read},
4 os,
5 path::Path,
6};
7
8use crate::error::{FileSystemError, FileSystemResult, IoOperation, IoResultExt};
9
10pub fn safe_remove<P: AsRef<Path>>(path: P) -> FileSystemResult<()> {
34 let path = path.as_ref();
35
36 let metadata = match fs::symlink_metadata(path) {
37 Ok(m) => m,
38 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
39 Err(e) => {
40 return Err(FileSystemError::RemoveFile {
41 path: path.to_path_buf(),
42 source: e,
43 })
44 }
45 };
46
47 let result = if metadata.is_dir() {
48 fs::remove_dir_all(path)
49 } else {
50 fs::remove_file(path)
51 };
52
53 result.with_path(path, IoOperation::RemoveFile)?;
54
55 Ok(())
56}
57
58pub fn ensure_dir_exists<P: AsRef<Path>>(path: P) -> FileSystemResult<()> {
85 let path = path.as_ref();
86 if !path.exists() {
87 std::fs::create_dir_all(path).with_path(path, IoOperation::CreateDirectory)?;
88 } else if !path.is_dir() {
89 return Err(FileSystemError::NotADirectory {
90 path: path.to_path_buf(),
91 });
92 }
93
94 Ok(())
95}
96
97pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
122 source: P,
123 target: Q,
124) -> FileSystemResult<()> {
125 let source = source.as_ref();
126 let target = target.as_ref();
127
128 if let Some(parent) = target.parent() {
129 ensure_dir_exists(parent)?;
130 }
131
132 if target.is_file() {
133 fs::remove_file(target).with_path(target, IoOperation::RemoveFile)?;
134 }
135
136 os::unix::fs::symlink(source, target).with_path(
137 source,
138 IoOperation::CreateSymlink {
139 target: target.into(),
140 },
141 )
142}
143
144pub fn walk_dir<P, F, E>(dir: P, action: &mut F) -> Result<(), E>
173where
174 P: AsRef<Path>,
175 F: FnMut(&Path) -> Result<(), E>,
176 FileSystemError: Into<E>,
177{
178 let dir = dir.as_ref();
179
180 if !dir.is_dir() {
181 return Err(FileSystemError::NotADirectory {
182 path: dir.to_path_buf(),
183 }
184 .into());
185 }
186
187 for entry in fs::read_dir(dir)
188 .with_path(dir, IoOperation::ReadDirectory)
189 .map_err(|e| e.into())?
190 {
191 let Ok(entry) = entry else {
192 continue;
193 };
194
195 let path = entry.path();
196
197 if path.is_dir() {
198 walk_dir(&path, action)?;
199 continue;
200 }
201
202 action(&path)?;
203 }
204
205 Ok(())
206}
207
208pub fn read_file_signature<P: AsRef<Path>>(path: P, bytes: usize) -> FileSystemResult<Vec<u8>> {
231 let path = path.as_ref();
232 let file = File::open(path).with_path(path, IoOperation::ReadFile)?;
233
234 let mut reader = BufReader::new(file);
235 let mut buffer = vec![0u8; bytes];
236 reader
237 .read_exact(&mut buffer)
238 .with_path(path, IoOperation::ReadFile)?;
239 Ok(buffer)
240}
241
242pub fn dir_size<P: AsRef<Path>>(path: P) -> FileSystemResult<u64> {
264 let path = path.as_ref();
265 let mut total_size = 0;
266
267 for entry in fs::read_dir(path).with_path(path, IoOperation::ReadDirectory)? {
268 let Ok(entry) = entry else {
269 continue;
270 };
271
272 let Ok(metadata) = entry.metadata() else {
273 continue;
274 };
275
276 if metadata.is_file() {
277 total_size += metadata.len();
278 } else if metadata.is_dir() {
279 total_size += dir_size(entry.path())?;
280 }
281 }
282
283 Ok(total_size)
284}
285
286pub fn is_elf<P: AsRef<Path>>(path: P) -> bool {
307 read_file_signature(path, 4)
308 .ok()
309 .map(|magic| magic == [0x7f, 0x45, 0x4c, 0x46])
310 .unwrap_or(false)
311}
312
313#[cfg(test)]
314mod tests {
315 use std::{fs::Permissions, os::unix::fs::PermissionsExt};
316
317 use tempfile::tempdir;
318
319 use super::*;
320
321 #[test]
322 fn test_safe_remove_file() {
323 let dir = tempdir().unwrap();
324 let file_path = dir.path().join("test_file.txt");
325 fs::write(&file_path, "hello").unwrap();
326 safe_remove(&file_path).unwrap();
327 assert!(!file_path.exists());
328 }
329
330 #[test]
331 fn test_safe_remove_dir() {
332 let dir = tempdir().unwrap();
333 let sub_dir = dir.path().join("sub");
334 fs::create_dir(&sub_dir).unwrap();
335 safe_remove(&sub_dir).unwrap();
336 assert!(!sub_dir.exists());
337 }
338
339 #[test]
340 fn test_safe_remove_non_existent() {
341 let dir = tempdir().unwrap();
342 let file_path = dir.path().join("non_existent.txt");
343 safe_remove(&file_path).unwrap();
344 }
345
346 #[test]
347 fn test_ensure_dir_exists() {
348 let dir = tempdir().unwrap();
349 let new_dir = dir.path().join("new_dir");
350 ensure_dir_exists(&new_dir).unwrap();
351 assert!(new_dir.is_dir());
352 }
353
354 #[test]
355 fn test_ensure_dir_exists_already_exists() {
356 let dir = tempdir().unwrap();
357 ensure_dir_exists(dir.path()).unwrap();
358 assert!(dir.path().is_dir());
359 }
360
361 #[test]
362 fn test_ensure_dir_exists_file_collision() {
363 let dir = tempdir().unwrap();
364 let file_path = dir.path().join("file.txt");
365 fs::write(&file_path, "hello").unwrap();
366 assert!(ensure_dir_exists(&file_path).is_err());
367 }
368
369 #[test]
370 fn test_ensure_dir_exists_permission_denied() {
371 let dir = tempdir().unwrap();
372 let read_only_dir = dir.path().join("read_only");
373 fs::create_dir(&read_only_dir).unwrap();
374
375 let mut perms = fs::metadata(&read_only_dir).unwrap().permissions();
377 perms.set_readonly(true);
378 fs::set_permissions(&read_only_dir, perms).unwrap();
379
380 let new_dir = read_only_dir.join("new_dir");
381 let result = ensure_dir_exists(&new_dir);
382 assert!(result.is_err());
383
384 let mut perms = fs::metadata(&read_only_dir).unwrap().permissions();
386 perms.set_mode(0o755);
387 fs::set_permissions(&read_only_dir, perms).unwrap();
388 }
389
390 #[test]
391 fn test_standard_safe_remove_permission_denied() {
392 let dir = tempdir().unwrap();
393 let sub_dir = dir.path().join("read_only_dir");
394 fs::create_dir(&sub_dir).unwrap();
395 let file_path = sub_dir.join("file.txt");
396 fs::write(&file_path, "content").unwrap();
397
398 let mut perms = fs::metadata(&sub_dir).unwrap().permissions();
400 perms.set_readonly(true);
401 fs::set_permissions(&sub_dir, perms).unwrap();
402
403 let result = safe_remove(&file_path);
404 assert!(result.is_err());
405
406 let mut perms = fs::metadata(&sub_dir).unwrap().permissions();
408 perms.set_mode(0o755);
409 fs::set_permissions(&sub_dir, perms).unwrap();
410 }
411
412 #[test]
413 fn test_safe_remove_dir_permission_denied() {
414 let dir = tempdir().unwrap();
415 let sub_dir = dir.path().join("read_only_dir");
416 fs::create_dir(&sub_dir).unwrap();
417 let file_path = sub_dir.join("file.txt");
418 fs::write(&file_path, "content").unwrap();
419
420 let mut perms = fs::metadata(&sub_dir).unwrap().permissions();
422 perms.set_readonly(true);
423 fs::set_permissions(&sub_dir, perms).unwrap();
424
425 let result = safe_remove(&sub_dir);
426 assert!(result.is_err());
427
428 let mut perms = fs::metadata(&sub_dir).unwrap().permissions();
430 perms.set_mode(0o755);
431 fs::set_permissions(&sub_dir, perms).unwrap();
432 }
433
434 #[test]
435 fn test_create_symlink() {
436 let dir = tempdir().unwrap();
437 let source = dir.path().join("source");
438 let target = dir.path().join("target");
439 fs::write(&source, "content").unwrap();
440 create_symlink(&source, &target).unwrap();
441 assert!(target.is_symlink());
442 assert_eq!(fs::read_link(&target).unwrap(), source);
443 }
444
445 #[test]
446 fn test_create_symlink_already_exists() {
447 let dir = tempdir().unwrap();
448 let source = dir.path().join("source");
449 let target = dir.path().join("target");
450 fs::write(&source, "content").unwrap();
451 fs::write(&target, "content").unwrap();
452 create_symlink(&source, &target).unwrap();
453 assert!(target.is_symlink());
454 assert_eq!(fs::read_link(&target).unwrap(), source);
455 }
456
457 #[test]
458 fn test_create_symlink_permission_denied() {
459 let dir = tempdir().unwrap();
460 let source = dir.path().join("source");
461 let target = dir.path().join("target");
462 fs::write(&source, "content").unwrap();
463
464 let mut perms = fs::metadata(dir.path()).unwrap().permissions();
466 perms.set_readonly(true);
467 fs::set_permissions(dir.path(), perms).unwrap();
468
469 let result = create_symlink(&source, &target);
470 assert!(result.is_err());
471
472 let mut perms = fs::metadata(dir.path()).unwrap().permissions();
474 perms.set_mode(0o755);
475 fs::set_permissions(dir.path(), perms).unwrap();
476 }
477
478 #[test]
479 fn test_walk_dir() {
480 let tempdir = tempfile::tempdir().unwrap();
481 let dir = tempdir.path().join("dir");
482 fs::create_dir(&dir).unwrap();
483 let file = dir.join("file");
484 fs::File::create(&file).unwrap();
485
486 let mut results = Vec::new();
487 walk_dir(&dir, &mut |path| -> FileSystemResult<()> {
488 results.push(path.to_path_buf());
489 Ok(())
490 })
491 .unwrap();
492
493 assert_eq!(results, vec![file]);
494 }
495
496 #[test]
497 fn test_walk_dir_not_a_dir() {
498 let tempdir = tempfile::tempdir().unwrap();
499 let file = tempdir.path().join("file");
500 fs::File::create(&file).unwrap();
501
502 let result = walk_dir(&file, &mut |_| -> FileSystemResult<()> { Ok(()) });
503 assert!(result.is_err());
504 }
505
506 #[test]
507 fn test_walk_recursive_dir() {
508 let tempdir = tempfile::tempdir().unwrap();
509 let dir = tempdir.path().join("dir");
510 fs::create_dir(&dir).unwrap();
511 let file = dir.join("file");
512 File::create(&file).unwrap();
513
514 let nested_dir = dir.join("nested");
515 fs::create_dir(&nested_dir).unwrap();
516 let nested_file = nested_dir.join("file");
517 File::create(&nested_file).unwrap();
518
519 let mut results = Vec::new();
520 walk_dir(&dir, &mut |path| -> FileSystemResult<()> {
521 results.push(path.to_path_buf());
522 Ok(())
523 })
524 .unwrap();
525
526 results.sort();
527 let mut expected = vec![file, nested_file];
528 expected.sort();
529 assert_eq!(results, expected);
530 }
531
532 #[test]
533 fn test_walk_failing_entry() {
534 let tempdir = tempfile::tempdir().unwrap();
535 let dir = tempdir.path().join("dir");
536 fs::create_dir(&dir).unwrap();
537 let file = dir.join("file");
538 File::create(&file).unwrap();
539
540 let mut results = Vec::new();
541 walk_dir(&dir, &mut |path| {
542 results.push(path.to_path_buf());
543 Err(FileSystemError::ReadFile {
544 path: path.to_path_buf(),
545 source: std::io::Error::from(std::io::ErrorKind::Other),
546 })
547 })
548 .ok();
549
550 assert_eq!(results, vec![file]);
551 }
552
553 #[test]
554 fn test_walk_invalid_dir() {
555 let result = walk_dir("/this/path/does/not/exist", &mut |_| -> FileSystemResult<
556 (),
557 > { Ok(()) });
558 assert!(result.is_err());
559 }
560
561 #[test]
562 fn test_walk_dir_permission_denied() {
563 let tempdir = tempfile::tempdir().unwrap();
564 let dir = tempdir.path();
565
566 fs::set_permissions(dir, Permissions::from_mode(0o000)).unwrap();
567
568 let result = walk_dir(dir, &mut |_| -> FileSystemResult<()> { Ok(()) });
569
570 fs::set_permissions(dir, Permissions::from_mode(0o755)).unwrap();
571 assert!(result.is_err());
572 }
573
574 #[test]
575 fn test_walk_dir_permission_denied_recursive() {
576 let tempdir = tempfile::tempdir().unwrap();
577 let dir = tempdir.path();
578 let nested_dir = dir.join("nested");
579 fs::create_dir(&nested_dir).unwrap();
580
581 fs::set_permissions(&nested_dir, Permissions::from_mode(0o000)).unwrap();
582
583 let result = walk_dir(dir, &mut |_| -> FileSystemResult<()> { Ok(()) });
584
585 fs::set_permissions(nested_dir, Permissions::from_mode(0o755)).unwrap();
586 assert!(result.is_err());
587 }
588
589 #[test]
590 fn test_read_file_signature() {
591 let tempdir = tempfile::tempdir().unwrap();
592 let file = tempdir.path().join("file");
593 File::create(&file).unwrap();
594 fs::write(&file, b"sample test content").unwrap();
595
596 let signature = read_file_signature(&file, 8).unwrap();
597 assert_eq!(signature.len(), 8);
598 assert_eq!(signature, b"sample t");
599 }
600
601 #[test]
602 fn test_read_file_signature_empty() {
603 let tempdir = tempfile::tempdir().unwrap();
604 let file = tempdir.path().join("file");
605 File::create(&file).unwrap();
606
607 let signature = read_file_signature(&file, 0).unwrap();
608 assert!(signature.is_empty());
609 }
610
611 #[test]
612 fn test_read_file_signature_invalid() {
613 let tempdir = tempfile::tempdir().unwrap();
614 let file = tempdir.path().join("file");
615 File::create(&file).unwrap();
616
617 let result = read_file_signature(&file, 1024);
618 assert!(result.is_err());
619 }
620
621 #[test]
622 fn test_read_file_signature_non_existent() {
623 let result = read_file_signature("/this/path/does/not/exist", 1024);
624 assert!(result.is_err());
625 }
626
627 #[test]
628 fn test_calculate_directory_size() {
629 let tempdir = tempfile::tempdir().unwrap();
630 let dir = tempdir.path().join("dir");
631 fs::create_dir(&dir).unwrap();
632
633 let file = dir.join("file");
634 File::create(&file).unwrap();
635 fs::write(&file, b"sample test content").unwrap(); let nested_dir = dir.join("nested");
638 fs::create_dir(&nested_dir).unwrap();
639
640 let nested_file = nested_dir.join("file");
641 File::create(&nested_file).unwrap();
642 fs::write(&nested_file, b"sample test content").unwrap();
643
644 let size = dir_size(&dir).unwrap();
645 assert_eq!(size, 38);
646 }
647
648 #[test]
649 fn test_calculate_directory_size_empty() {
650 let tempdir = tempfile::tempdir().unwrap();
651 let dir = tempdir.path().join("dir");
652 fs::create_dir(&dir).unwrap();
653
654 let size = dir_size(&dir).unwrap();
655 assert_eq!(size, 0);
656 }
657
658 #[test]
659 fn test_calculate_directory_size_invalid() {
660 let result = dir_size("/this/path/does/not/exist");
661 assert!(result.is_err());
662 }
663
664 #[test]
665 fn test_calculate_directory_size_inner_permission_denied() {
666 let tempdir = tempfile::tempdir().unwrap();
667 let dir = tempdir.path();
668 let inner_dir = dir.join("inner");
669 ensure_dir_exists(&inner_dir).unwrap();
670
671 fs::set_permissions(&inner_dir, Permissions::from_mode(0o000)).unwrap();
672
673 let result = dir_size(dir);
674 assert!(result.is_err());
675
676 fs::set_permissions(inner_dir, Permissions::from_mode(0o755)).unwrap();
678 }
679
680 #[test]
681 fn test_create_symlink_inner_target() {
682 let tempdir = tempfile::tempdir().unwrap();
683 let source = tempdir.path().join("source");
684 let target = tempdir.path().join("inner").join("target");
685
686 let result = create_symlink(&source, &target);
687 assert!(result.is_ok());
688 }
689
690 #[test]
691 fn test_create_symlink_target_invalid_parent() {
692 let tempdir = tempfile::tempdir().unwrap();
693 let source = tempdir.path().join("source");
694
695 let file = tempdir.path().join("file");
696 File::create(&file).unwrap();
697 let target = tempdir.path().join("file").join("target");
698
699 let result = create_symlink(&source, &target);
700 assert!(result.is_err());
701 }
702
703 #[test]
704 fn test_create_symlink_target_no_permissions() {
705 let tempdir = tempfile::tempdir().unwrap();
706 let source = tempdir.path().join("source");
707 let target = tempdir.path().join("target");
708 File::create(&target).unwrap();
709
710 let mut perms = fs::metadata(tempdir.path()).unwrap().permissions();
712 perms.set_readonly(true);
713 fs::set_permissions(tempdir.path(), perms).unwrap();
714
715 let result = create_symlink(&source, &target);
716 assert!(result.is_err());
717
718 let mut perms = fs::metadata(tempdir.path()).unwrap().permissions();
720 perms.set_mode(0o755);
721 fs::set_permissions(tempdir.path(), perms).unwrap();
722 }
723}