1use memmap2::{Mmap, MmapOptions};
10use std::fs::File;
11use std::path::{Path, PathBuf};
12use thiserror::Error;
13
14type BufferOffset = usize;
16
17type BufferLength = usize;
19
20type FileSize = u64;
22
23#[derive(Debug, Error)]
25#[non_exhaustive]
26pub enum IoError {
27 #[error("Failed to open file '{path}': {source}")]
29 FileOpenError {
30 path: PathBuf,
32 #[source]
34 source: std::io::Error,
35 },
36
37 #[error("Failed to memory-map file '{path}': {source}")]
39 MmapError {
40 path: PathBuf,
42 #[source]
44 source: std::io::Error,
45 },
46
47 #[error("File '{path}' is empty")]
49 EmptyFile {
50 path: PathBuf,
52 },
53
54 #[error("File '{path}' is too large ({size} bytes, maximum {max_size} bytes)")]
56 FileTooLarge {
57 path: PathBuf,
59 size: FileSize,
61 max_size: FileSize,
63 },
64
65 #[error("Failed to read metadata for file '{path}': {source}")]
67 MetadataError {
68 path: PathBuf,
70 #[source]
72 source: std::io::Error,
73 },
74
75 #[error(
77 "Buffer access out of bounds: offset {offset} + length {length} > buffer size {buffer_size}"
78 )]
79 BufferOverrun {
80 offset: BufferOffset,
82 length: BufferLength,
84 buffer_size: BufferLength,
86 },
87
88 #[error("Invalid buffer access parameters: offset {offset}, length {length}")]
90 InvalidAccess {
91 offset: BufferOffset,
93 length: BufferLength,
95 },
96
97 #[error("File '{path}' is not a regular file (file type: {file_type})")]
99 InvalidFileType {
100 path: PathBuf,
102 file_type: String,
104 },
105}
106
107#[derive(Debug)]
125pub struct FileBuffer {
126 mmap: Mmap,
128 path: PathBuf,
130}
131
132impl FileBuffer {
133 pub const MAX_FILE_SIZE: FileSize = 1024 * 1024 * 1024;
139
140 pub fn new(path: &Path) -> Result<Self, IoError> {
170 let path_buf = path.to_path_buf();
171
172 let file = Self::open_file(path, &path_buf)?;
173 Self::validate_file_metadata(&file, &path_buf)?;
174 let mmap = Self::create_memory_mapping(&file, &path_buf)?;
175
176 Ok(Self {
177 mmap,
178 path: path_buf,
179 })
180 }
181
182 pub fn from_path_and_metadata(
210 path: &Path,
211 metadata: &std::fs::Metadata,
212 ) -> Result<Self, IoError> {
213 let path_buf = path.to_path_buf();
214 Self::check_metadata(metadata, &path_buf)?;
215 let file = Self::open_file(path, &path_buf)?;
216 let mmap = Self::create_memory_mapping(&file, &path_buf)?;
217
218 Ok(Self {
219 mmap,
220 path: path_buf,
221 })
222 }
223
224 fn open_file(path: &Path, path_buf: &Path) -> Result<File, IoError> {
226 File::open(path).map_err(|source| IoError::FileOpenError {
227 path: path_buf.to_path_buf(),
228 source,
229 })
230 }
231
232 fn validate_file_metadata(file: &File, path_buf: &Path) -> Result<(), IoError> {
239 let metadata = file.metadata().map_err(|source| IoError::MetadataError {
240 path: path_buf.to_path_buf(),
241 source,
242 })?;
243
244 Self::check_metadata(&metadata, path_buf)
245 }
246
247 fn check_metadata(metadata: &std::fs::Metadata, reported_path: &Path) -> Result<(), IoError> {
255 if !metadata.is_file() {
257 let file_type = if metadata.is_dir() {
258 "directory".to_string()
259 } else if metadata.is_symlink() {
260 "symlink".to_string()
261 } else {
262 Self::detect_special_file_type(metadata)
264 };
265
266 return Err(IoError::InvalidFileType {
267 path: reported_path.to_path_buf(),
268 file_type,
269 });
270 }
271
272 let file_size = metadata.len();
273
274 if file_size == 0 {
276 return Err(IoError::EmptyFile {
277 path: reported_path.to_path_buf(),
278 });
279 }
280
281 if file_size > Self::MAX_FILE_SIZE {
283 return Err(IoError::FileTooLarge {
284 path: reported_path.to_path_buf(),
285 size: file_size,
286 max_size: Self::MAX_FILE_SIZE,
287 });
288 }
289
290 Ok(())
291 }
292
293 fn detect_special_file_type(metadata: &std::fs::Metadata) -> String {
295 #[cfg(unix)]
296 {
297 use std::os::unix::fs::FileTypeExt;
298 if metadata.file_type().is_block_device() {
299 "block device".to_string()
300 } else if metadata.file_type().is_char_device() {
301 "character device".to_string()
302 } else if metadata.file_type().is_fifo() {
303 "FIFO/pipe".to_string()
304 } else if metadata.file_type().is_socket() {
305 "socket".to_string()
306 } else {
307 "special file".to_string()
308 }
309 }
310 #[cfg(windows)]
311 {
312 if metadata.file_type().is_symlink() {
313 "symlink".to_string()
314 } else {
315 "special file".to_string()
316 }
317 }
318 #[cfg(not(any(unix, windows)))]
319 {
320 "special file".to_string()
321 }
322 }
323
324 #[cfg(test)]
335 pub(crate) fn create_symlink<P: AsRef<std::path::Path>, Q: AsRef<std::path::Path>>(
336 original: P,
337 link: Q,
338 ) -> Result<(), std::io::Error> {
339 #[cfg(unix)]
340 {
341 std::os::unix::fs::symlink(original, link)
342 }
343 #[cfg(windows)]
344 {
345 let original_path = original.as_ref();
346
347 if original_path.is_dir() {
348 std::os::windows::fs::symlink_dir(original, link)
349 } else {
350 std::os::windows::fs::symlink_file(original, link)
351 }
352 }
353 #[cfg(not(any(unix, windows)))]
354 {
355 Err(std::io::Error::new(
356 std::io::ErrorKind::Unsupported,
357 "Symlinks not supported on this platform",
358 ))
359 }
360 }
361
362 fn create_memory_mapping(file: &File, path_buf: &Path) -> Result<Mmap, IoError> {
371 #[allow(unsafe_code)]
376 unsafe {
377 MmapOptions::new().map(file).map_err(|source| {
378 let sanitized_path = path_buf.file_name().map_or_else(
380 || "<unknown>".to_string(),
381 |name| name.to_string_lossy().into_owned(),
382 );
383
384 IoError::MmapError {
385 path: PathBuf::from(sanitized_path),
386 source,
387 }
388 })
389 }
390 }
391
392 #[must_use]
409 pub fn as_slice(&self) -> &[u8] {
410 &self.mmap
411 }
412
413 #[must_use]
426 pub fn path(&self) -> &Path {
427 &self.path
428 }
429
430 #[must_use]
443 pub fn len(&self) -> usize {
444 self.mmap.len()
445 }
446
447 #[must_use]
463 pub fn is_empty(&self) -> bool {
464 self.mmap.is_empty()
465 }
466}
467
468pub fn safe_read_bytes(
506 buffer: &[u8],
507 offset: BufferOffset,
508 length: BufferLength,
509) -> Result<&[u8], IoError> {
510 validate_buffer_access(buffer.len(), offset, length)?;
511 let end_offset = offset + length; Ok(buffer.get(offset..end_offset).unwrap_or(&[]))
515}
516
517pub fn safe_read_byte(buffer: &[u8], offset: BufferOffset) -> Result<u8, IoError> {
547 buffer.get(offset).copied().ok_or(IoError::BufferOverrun {
548 offset,
549 length: 1,
550 buffer_size: buffer.len(),
551 })
552}
553
554pub fn validate_buffer_access(
592 buffer_size: BufferLength,
593 offset: BufferOffset,
594 length: BufferLength,
595) -> Result<(), IoError> {
596 if length == 0 {
598 return Err(IoError::InvalidAccess { offset, length });
599 }
600
601 if offset >= buffer_size {
603 return Err(IoError::BufferOverrun {
604 offset,
605 length,
606 buffer_size,
607 });
608 }
609
610 let end_offset = offset
612 .checked_add(length)
613 .ok_or(IoError::InvalidAccess { offset, length })?;
614
615 if end_offset > buffer_size {
617 return Err(IoError::BufferOverrun {
618 offset,
619 length,
620 buffer_size,
621 });
622 }
623
624 Ok(())
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630 use std::fs;
631 use std::io::Write;
632 use std::sync::atomic::{AtomicU64, Ordering};
633
634 static TEMP_FILE_COUNTER: AtomicU64 = AtomicU64::new(0);
636
637 fn create_temp_file(content: &[u8]) -> PathBuf {
640 let temp_dir = std::env::temp_dir();
641 let id = TEMP_FILE_COUNTER.fetch_add(1, Ordering::Relaxed);
642 let file_path = temp_dir.join(format!("libmagic_test_{}_{id}", std::process::id()));
643
644 {
645 let mut file = File::create(&file_path).expect("Failed to create temp file");
646 file.write_all(content).expect("Failed to write temp file");
647 file.sync_all().expect("Failed to sync temp file");
648 } file_path
651 }
652
653 fn cleanup_temp_file(path: &Path) {
655 let _ = fs::remove_file(path);
656 }
657
658 #[test]
659 fn test_file_buffer_creation_success() {
660 let content = b"Hello, World!";
661 let temp_path = create_temp_file(content);
662
663 let buffer = FileBuffer::new(&temp_path).expect("Failed to create FileBuffer");
664
665 assert_eq!(buffer.as_slice(), content);
666 assert_eq!(buffer.len(), content.len());
667 assert!(!buffer.is_empty());
668 assert_eq!(buffer.path(), temp_path.as_path());
669
670 cleanup_temp_file(&temp_path);
671 }
672
673 #[test]
674 fn test_file_buffer_nonexistent_file() {
675 let nonexistent_path = Path::new("/nonexistent/file.bin");
676
677 let result = FileBuffer::new(nonexistent_path);
678
679 assert!(result.is_err());
680 match result.unwrap_err() {
681 IoError::FileOpenError { path, .. } => {
682 assert_eq!(path, nonexistent_path);
683 }
684 other => panic!("Expected FileOpenError, got {other:?}"),
685 }
686 }
687
688 #[test]
689 fn test_file_buffer_empty_file() {
690 let temp_path = create_temp_file(&[]);
691
692 let result = FileBuffer::new(&temp_path);
693
694 assert!(result.is_err());
695 match result.unwrap_err() {
696 IoError::EmptyFile { path } => {
697 assert_eq!(path, temp_path);
701 }
702 other => panic!("Expected EmptyFile error, got {other:?}"),
703 }
704
705 cleanup_temp_file(&temp_path);
706 }
707
708 #[test]
709 fn test_file_buffer_large_file() {
710 let content = vec![0u8; 1024]; let temp_path = create_temp_file(&content);
713
714 let buffer =
715 FileBuffer::new(&temp_path).expect("Failed to create FileBuffer for normal file");
716 assert_eq!(buffer.len(), 1024);
717
718 cleanup_temp_file(&temp_path);
719 }
720
721 #[test]
722 fn test_file_buffer_binary_content() {
723 let content = vec![0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD, 0xFC];
724 let temp_path = create_temp_file(&content);
725
726 let buffer = FileBuffer::new(&temp_path).expect("Failed to create FileBuffer");
727
728 assert_eq!(buffer.as_slice(), content.as_slice());
729 assert_eq!(buffer.as_slice()[0], 0x00);
730 assert_eq!(buffer.as_slice()[7], 0xFC);
731
732 cleanup_temp_file(&temp_path);
733 }
734
735 #[test]
736 fn test_io_error_display() {
737 let path = PathBuf::from("/test/path");
738 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
739
740 let error = IoError::FileOpenError {
741 path,
742 source: io_err,
743 };
744
745 let error_string = format!("{error}");
746 assert!(error_string.contains("/test/path"));
747 assert!(error_string.contains("Failed to open file"));
748 }
749
750 #[test]
751 fn test_empty_file_error_display() {
752 let path = PathBuf::from("/test/empty.bin");
753 let error = IoError::EmptyFile { path };
754
755 let error_string = format!("{error}");
756 assert!(error_string.contains("/test/empty.bin"));
757 assert!(error_string.contains("is empty"));
758 }
759
760 #[test]
761 fn test_file_too_large_error_display() {
762 let path = PathBuf::from("/test/large.bin");
763 let error = IoError::FileTooLarge {
764 path,
765 size: 2_000_000_000,
766 max_size: 1_000_000_000,
767 };
768
769 let error_string = format!("{error}");
770 assert!(error_string.contains("/test/large.bin"));
771 assert!(error_string.contains("too large"));
772 assert!(error_string.contains("2000000000"));
773 assert!(error_string.contains("1000000000"));
774 }
775
776 #[test]
777 fn test_safe_read_bytes_success() {
778 let buffer = b"Hello, World!";
779
780 let result = safe_read_bytes(buffer, 0, 5).expect("Failed to read bytes");
782 assert_eq!(result, b"Hello");
783
784 let result = safe_read_bytes(buffer, 7, 5).expect("Failed to read bytes");
786 assert_eq!(result, b"World");
787
788 let result = safe_read_bytes(buffer, 0, 1).expect("Failed to read bytes");
790 assert_eq!(result, b"H");
791
792 let result = safe_read_bytes(buffer, 0, buffer.len()).expect("Failed to read bytes");
794 assert_eq!(result, buffer);
795
796 let result = safe_read_bytes(buffer, buffer.len() - 1, 1).expect("Failed to read bytes");
798 assert_eq!(result, b"!");
799 }
800
801 #[test]
802 fn test_safe_read_bytes_out_of_bounds() {
803 let buffer = b"Hello";
804
805 let result = safe_read_bytes(buffer, 10, 1);
807 assert!(result.is_err());
808 match result.unwrap_err() {
809 IoError::BufferOverrun {
810 offset,
811 length,
812 buffer_size,
813 } => {
814 assert_eq!(offset, 10);
815 assert_eq!(length, 1);
816 assert_eq!(buffer_size, 5);
817 }
818 other => panic!("Expected BufferOverrun, got {other:?}"),
819 }
820
821 let result = safe_read_bytes(buffer, 3, 5);
823 assert!(result.is_err());
824 match result.unwrap_err() {
825 IoError::BufferOverrun {
826 offset,
827 length,
828 buffer_size,
829 } => {
830 assert_eq!(offset, 3);
831 assert_eq!(length, 5);
832 assert_eq!(buffer_size, 5);
833 }
834 other => panic!("Expected BufferOverrun, got {other:?}"),
835 }
836
837 let result = safe_read_bytes(buffer, 5, 1);
839 assert!(result.is_err());
840 }
841
842 #[test]
843 fn test_safe_read_bytes_zero_length() {
844 let buffer = b"Hello";
845
846 let result = safe_read_bytes(buffer, 0, 0);
847 assert!(result.is_err());
848 match result.unwrap_err() {
849 IoError::InvalidAccess { offset, length } => {
850 assert_eq!(offset, 0);
851 assert_eq!(length, 0);
852 }
853 other => panic!("Expected InvalidAccess, got {other:?}"),
854 }
855 }
856
857 #[test]
858 fn test_safe_read_bytes_overflow() {
859 let buffer = b"Hello";
860
861 let result = safe_read_bytes(buffer, usize::MAX, 1);
864 assert!(result.is_err());
865 match result.unwrap_err() {
866 IoError::BufferOverrun { .. } => {
867 }
869 other => panic!("Expected BufferOverrun, got {other:?}"),
870 }
871
872 let result = safe_read_bytes(buffer, 1, usize::MAX);
874 assert!(result.is_err());
875 match result.unwrap_err() {
876 IoError::InvalidAccess { .. } => {
877 }
879 other => panic!("Expected InvalidAccess, got {other:?}"),
880 }
881
882 let result = safe_read_bytes(buffer, 2, usize::MAX - 1);
884 assert!(result.is_err());
885 match result.unwrap_err() {
886 IoError::InvalidAccess { .. } => {
887 }
889 other => panic!("Expected InvalidAccess, got {other:?}"),
890 }
891 }
892
893 #[test]
894 fn test_safe_read_byte_success() {
895 let buffer = b"Hello";
896
897 assert_eq!(safe_read_byte(buffer, 0).unwrap(), b'H');
898 assert_eq!(safe_read_byte(buffer, 1).unwrap(), b'e');
899 assert_eq!(safe_read_byte(buffer, 4).unwrap(), b'o');
900 }
901
902 #[test]
903 fn test_safe_read_byte_out_of_bounds() {
904 let buffer = b"Hello";
905
906 let result = safe_read_byte(buffer, 5);
907 assert!(result.is_err());
908 match result.unwrap_err() {
909 IoError::BufferOverrun {
910 offset,
911 length,
912 buffer_size,
913 } => {
914 assert_eq!(offset, 5);
915 assert_eq!(length, 1);
916 assert_eq!(buffer_size, 5);
917 }
918 other => panic!("Expected BufferOverrun, got {other:?}"),
919 }
920
921 let result = safe_read_byte(buffer, 100);
922 assert!(result.is_err());
923 }
924
925 #[test]
926 fn test_validate_buffer_access_success() {
927 validate_buffer_access(100, 0, 50).expect("Should be valid");
929 validate_buffer_access(100, 50, 50).expect("Should be valid");
930 validate_buffer_access(100, 99, 1).expect("Should be valid");
931 validate_buffer_access(10, 0, 10).expect("Should be valid");
932 validate_buffer_access(1, 0, 1).expect("Should be valid");
933 }
934
935 #[test]
936 fn test_validate_buffer_access_invalid() {
937 let result = validate_buffer_access(100, 0, 0);
939 assert!(result.is_err());
940
941 let result = validate_buffer_access(100, 100, 1);
943 assert!(result.is_err());
944
945 let result = validate_buffer_access(100, 50, 51);
947 assert!(result.is_err());
948
949 let result = validate_buffer_access(100, usize::MAX, 1);
951 assert!(result.is_err());
952
953 let result = validate_buffer_access(100, 1, usize::MAX);
954 assert!(result.is_err());
955 }
956
957 #[test]
958 fn test_validate_buffer_access_edge_cases() {
959 let result = validate_buffer_access(0, 0, 1);
961 assert!(result.is_err());
962
963 let large_size = 1_000_000;
965 validate_buffer_access(large_size, 0, large_size).expect("Should be valid");
966 validate_buffer_access(large_size, large_size - 1, 1).expect("Should be valid");
967
968 let result = validate_buffer_access(large_size, large_size - 1, 2);
970 assert!(result.is_err());
971 }
972
973 #[test]
974 fn test_buffer_access_security_patterns() {
975 let buffer_size = 1024;
977
978 let overflow_patterns = vec![
980 (usize::MAX, 1), (buffer_size, usize::MAX), (usize::MAX - 1, 2), ];
984
985 for (offset, length) in overflow_patterns {
986 let result = validate_buffer_access(buffer_size, offset, length);
987 assert!(
988 result.is_err(),
989 "Should reject potentially dangerous access pattern: offset={offset}, length={length}"
990 );
991 }
992
993 let safe_patterns = vec![
995 (0, 1), (buffer_size - 1, 1), (buffer_size / 2, 1), ];
999
1000 for (offset, length) in safe_patterns {
1001 let result = validate_buffer_access(buffer_size, offset, length);
1002 assert!(
1003 result.is_ok(),
1004 "Should accept safe access pattern: offset={offset}, length={length}"
1005 );
1006 }
1007 }
1008
1009 #[test]
1010 fn test_buffer_overrun_error_display() {
1011 let error = IoError::BufferOverrun {
1012 offset: 10,
1013 length: 5,
1014 buffer_size: 12,
1015 };
1016
1017 let error_string = format!("{error}");
1018 assert!(error_string.contains("Buffer access out of bounds"));
1019 assert!(error_string.contains("offset 10"));
1020 assert!(error_string.contains("length 5"));
1021 assert!(error_string.contains("buffer size 12"));
1022 }
1023
1024 #[test]
1025 fn test_invalid_access_error_display() {
1026 let error = IoError::InvalidAccess {
1027 offset: 0,
1028 length: 0,
1029 };
1030
1031 let error_string = format!("{error}");
1032 assert!(error_string.contains("Invalid buffer access parameters"));
1033 assert!(error_string.contains("offset 0"));
1034 assert!(error_string.contains("length 0"));
1035 }
1036
1037 #[test]
1038 fn test_invalid_file_type_error_display() {
1039 let error = IoError::InvalidFileType {
1040 path: std::path::PathBuf::from("/dev/null"),
1041 file_type: "character device".to_string(),
1042 };
1043
1044 let error_string = format!("{error}");
1045 assert!(error_string.contains("is not a regular file"));
1046 assert!(error_string.contains("/dev/null"));
1047 assert!(error_string.contains("character device"));
1048 }
1049
1050 #[test]
1051 fn test_file_buffer_directory_rejection() {
1052 let temp_dir = std::env::temp_dir().join("test_dir_12345");
1054 std::fs::create_dir_all(&temp_dir).unwrap();
1055
1056 let result = FileBuffer::new(&temp_dir);
1057
1058 assert!(result.is_err());
1059 match result.unwrap_err() {
1060 IoError::InvalidFileType { path, file_type } => {
1061 assert_eq!(file_type, "directory");
1062 assert_eq!(path, temp_dir);
1065 }
1066 IoError::FileOpenError { .. } => {
1067 println!(
1070 "Directory test skipped on this platform (can't open directories as files)"
1071 );
1072 }
1073 other => panic!("Expected InvalidFileType or FileOpenError, got {other:?}"),
1074 }
1075
1076 std::fs::remove_dir(&temp_dir).unwrap();
1078 }
1079
1080 #[test]
1081 fn test_file_buffer_symlink_to_directory_rejection() {
1082 let temp_dir = std::env::temp_dir().join("test_dir_symlink_12345");
1084 let symlink_path = std::env::temp_dir().join("test_symlink_12345");
1085
1086 std::fs::create_dir_all(&temp_dir).unwrap();
1087
1088 let symlink_result = FileBuffer::create_symlink(&temp_dir, &symlink_path);
1090
1091 match symlink_result {
1092 Ok(()) => {
1093 let result = FileBuffer::new(&symlink_path);
1094
1095 assert!(result.is_err());
1096 match result.unwrap_err() {
1097 IoError::InvalidFileType { path, file_type } => {
1098 assert_eq!(file_type, "directory");
1099 assert_eq!(path, symlink_path);
1102 }
1103 IoError::FileOpenError { .. } => {
1104 println!(
1107 "Directory symlink test skipped on this platform (can't open directories as files)"
1108 );
1109 }
1110 other => panic!("Expected InvalidFileType or FileOpenError, got {other:?}"),
1111 }
1112
1113 let _ = std::fs::remove_file(&symlink_path);
1115 }
1116 Err(_) => {
1117 println!(
1119 "Skipping symlink test - unable to create symlink (may need admin privileges)"
1120 );
1121 }
1122 }
1123
1124 std::fs::remove_dir(&temp_dir).unwrap();
1126 }
1127
1128 #[test]
1129 fn test_file_buffer_symlink_to_regular_file_success() {
1130 let temp_file = std::env::temp_dir().join("test_file_symlink_12345");
1132 let symlink_path = std::env::temp_dir().join("test_symlink_file_12345");
1133
1134 let content = b"test content";
1135 std::fs::write(&temp_file, content).unwrap();
1136
1137 let symlink_result = FileBuffer::create_symlink(&temp_file, &symlink_path);
1139
1140 match symlink_result {
1141 Ok(()) => {
1142 let result = FileBuffer::new(&symlink_path);
1143
1144 assert!(result.is_ok());
1145 let buffer = result.unwrap();
1146 assert_eq!(buffer.as_slice(), content);
1147
1148 let _ = std::fs::remove_file(&symlink_path);
1150 }
1151 Err(_) => {
1152 println!(
1154 "Skipping symlink test - unable to create symlink (may need admin privileges)"
1155 );
1156 }
1157 }
1158
1159 std::fs::remove_file(&temp_file).unwrap();
1161 }
1162
1163 #[test]
1164 fn test_file_buffer_special_files_rejection() {
1165 #[cfg(unix)]
1167 {
1168 let result = FileBuffer::new(std::path::Path::new("/dev/null"));
1170 assert!(result.is_err());
1171 match result.unwrap_err() {
1172 IoError::InvalidFileType { path, file_type } => {
1173 assert_eq!(file_type, "character device");
1174 assert_eq!(path, std::path::PathBuf::from("/dev/null"));
1175 }
1176 other => panic!("Expected InvalidFileType error, got {other:?}"),
1177 }
1178
1179 let result = FileBuffer::new(std::path::Path::new("/dev/zero"));
1181 assert!(result.is_err());
1182 match result.unwrap_err() {
1183 IoError::InvalidFileType { path, file_type } => {
1184 assert_eq!(file_type, "character device");
1185 assert_eq!(path, std::path::PathBuf::from("/dev/zero"));
1186 }
1187 other => panic!("Expected InvalidFileType error, got {other:?}"),
1188 }
1189
1190 let result = FileBuffer::new(std::path::Path::new("/dev/random"));
1192 assert!(result.is_err());
1193 match result.unwrap_err() {
1194 IoError::InvalidFileType { path, file_type } => {
1195 assert_eq!(file_type, "character device");
1196 assert_eq!(path, std::path::PathBuf::from("/dev/random"));
1197 }
1198 other => panic!("Expected InvalidFileType error, got {other:?}"),
1199 }
1200 }
1201
1202 #[cfg(not(unix))]
1203 {
1204 println!("Skipping special file tests on non-Unix platform");
1206 }
1207 }
1208
1209 #[test]
1210 fn test_file_buffer_cross_platform_special_files() {
1211 let temp_dir = std::env::temp_dir().join("test_special_dir_12345");
1216 std::fs::create_dir_all(&temp_dir).unwrap();
1217
1218 let result = FileBuffer::new(&temp_dir);
1219 assert!(result.is_err());
1220 match result.unwrap_err() {
1221 IoError::InvalidFileType { path, file_type } => {
1222 assert_eq!(file_type, "directory");
1223 assert_eq!(path, temp_dir);
1225 }
1226 IoError::FileOpenError { .. } => {
1227 println!(
1229 "Directory test skipped on this platform (can't open directories as files)"
1230 );
1231 }
1232 other => panic!("Expected InvalidFileType or FileOpenError, got {other:?}"),
1233 }
1234
1235 std::fs::remove_dir(&temp_dir).unwrap();
1237 }
1238
1239 #[test]
1240 #[ignore = "FIFOs can cause hanging issues in CI environments"]
1241 fn test_file_buffer_fifo_rejection() {
1242 #[cfg(unix)]
1244 {
1245 use nix::unistd;
1246
1247 let fifo_path = std::env::temp_dir().join("test_fifo_12345");
1248
1249 match unistd::mkfifo(
1251 &fifo_path,
1252 nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR,
1253 ) {
1254 Ok(()) => {
1255 let result = FileBuffer::new(&fifo_path);
1256
1257 assert!(result.is_err());
1258 match result.unwrap_err() {
1259 IoError::InvalidFileType { path, file_type } => {
1260 assert_eq!(file_type, "FIFO/pipe");
1261 assert_eq!(path, fifo_path);
1263 }
1264 other => panic!("Expected InvalidFileType error, got {other:?}"),
1265 }
1266
1267 std::fs::remove_file(&fifo_path).unwrap();
1269 }
1270 Err(_) => {
1271 println!("Skipping FIFO test - unable to create FIFO");
1273 }
1274 }
1275 }
1276
1277 #[cfg(not(unix))]
1278 {
1279 println!("Skipping FIFO test on non-Unix platform");
1281 }
1282 }
1283}