abi_definitions/file_system/
directory.rs

1use crate::{XilaFileIdentifier, XilaFileSystemMode, XilaFileSystemOpen, XilaFileSystemStatus};
2use abi_context::{FileIdentifier, get_instance as get_context_instance};
3use alloc::{borrow::ToOwned, ffi::CString};
4use core::{
5    ffi::{CStr, c_char},
6    ptr::null_mut,
7};
8use file_system::{AccessFlags, CreateFlags, Error, Flags, Path, StateFlags};
9
10use log::debug;
11use virtual_file_system::{
12    SynchronousDirectory, SynchronousFile, get_instance as get_file_system_instance,
13};
14
15use super::{XilaFileKind, XilaFileSystemInode, XilaFileSystemSize, into_u32};
16
17/// This function is used to open a file or directory at a given directory.
18///
19/// # Safety
20///
21/// This function is unsafe because it dereferences raw pointers.
22#[unsafe(no_mangle)]
23pub unsafe extern "C" fn xila_file_system_open_at(
24    directory: XilaFileIdentifier,
25    relative_path: *const c_char,
26    is_directory: bool,
27    mode: XilaFileSystemMode,
28    open: XilaFileSystemOpen,
29    status: XilaFileSystemStatus,
30    out: *mut XilaFileIdentifier,
31) -> u32 {
32    into_u32(move || unsafe {
33        if relative_path.is_null() || out.is_null() {
34            Err(Error::InvalidParameter)?;
35        }
36
37        let relative_path = CStr::from_ptr(relative_path)
38            .to_str()
39            .map_err(|_| Error::InvalidParameter)?;
40
41        let context = get_context_instance();
42
43        let task = context.get_current_task_identifier();
44
45        let virtual_file_system = get_file_system_instance();
46
47        let (parent_identifier, full_path) = if relative_path == "/" {
48            (None, Path::ROOT.to_owned())
49        } else {
50            let identifier: FileIdentifier = directory.try_into()?;
51            let path = context
52                .resolve_path(task, identifier, relative_path)
53                .unwrap();
54            //.ok_or(Error::InvalidParameter)?;
55            (Some(identifier), path)
56        };
57
58        if is_directory {
59            let directory = SynchronousDirectory::open(virtual_file_system, task, &full_path)?;
60
61            *out = context
62                .insert_directory(task, parent_identifier, relative_path, directory)
63                .ok_or(Error::InvalidIdentifier)?
64                .into();
65        } else {
66            let mode = AccessFlags::from_bits_truncate(mode);
67            let open = CreateFlags::from_bits_truncate(open);
68            let status = StateFlags::from_bits_truncate(status);
69
70            let flags = Flags::new(mode, Some(open), Some(status));
71
72            let file = SynchronousFile::open(virtual_file_system, task, &full_path, flags)?;
73
74            *out = context
75                .insert_file(task, file, None)
76                .ok_or(Error::InvalidIdentifier)?
77                .into();
78        }
79
80        Ok(())
81    })
82}
83
84/// This function is used to open a directory.
85///
86/// # Safety
87///
88/// This function is unsafe because it dereferences raw pointers.
89#[unsafe(no_mangle)]
90pub unsafe extern "C" fn xila_file_system_open_directory(
91    path: *const c_char,
92    directory: *mut XilaFileIdentifier,
93) -> u32 {
94    unsafe {
95        into_u32(move || {
96            if path.is_null() || directory.is_null() {
97                Err(Error::InvalidParameter)?;
98            }
99
100            let path = CStr::from_ptr(path)
101                .to_str()
102                .map_err(|_| Error::InvalidParameter)?;
103
104            let context = get_context_instance();
105
106            let task = context.get_current_task_identifier();
107
108            debug!("Opening directory {path:?} for task {task:?}");
109
110            let d = SynchronousDirectory::open(get_file_system_instance(), task, path)?;
111
112            *directory = context
113                .insert_directory(task, None, path, d)
114                .ok_or(Error::InvalidIdentifier)?
115                .into();
116            Ok(())
117        })
118    }
119}
120
121/// This function is used to read a directory.
122///
123/// # Safety
124///
125/// This function is unsafe because it dereferences raw pointers.
126#[unsafe(no_mangle)]
127pub unsafe extern "C" fn xila_file_system_read_directory(
128    directory: XilaFileIdentifier,
129    entry_name: *mut *const c_char,
130    entry_type: *mut XilaFileKind,
131    entry_size: *mut XilaFileSystemSize,
132    entry_inode: *mut XilaFileSystemInode,
133) -> u32 {
134    unsafe {
135        into_u32(move || {
136            let task = get_context_instance().get_current_task_identifier();
137
138            debug!("Reading directory {directory:?} for task {task:?}");
139
140            let entry = get_context_instance()
141                .perform_operation_on_directory(directory.try_into()?, SynchronousDirectory::read)
142                .ok_or(Error::InvalidIdentifier)??;
143
144            if let Some(entry) = entry {
145                *entry_name = CString::new(entry.name.as_str()).unwrap().into_raw();
146                *entry_type = entry.kind.into();
147                *entry_size = entry.size;
148                *entry_inode = entry.inode;
149            } else {
150                *entry_name = null_mut();
151            }
152
153            Ok(())
154        })
155    }
156}
157
158#[unsafe(no_mangle)]
159pub extern "C" fn xila_file_system_close_directory(directory: XilaFileIdentifier) -> u32 {
160    into_u32(move || {
161        let d = get_context_instance()
162            .remove_directory(directory.try_into()?)
163            .ok_or(Error::InvalidIdentifier)?;
164
165        d.close(get_file_system_instance())?;
166
167        Ok(())
168    })
169}
170
171#[unsafe(no_mangle)]
172pub extern "C" fn xila_file_system_rewind_directory(directory: XilaFileIdentifier) -> u32 {
173    into_u32(move || {
174        debug!("Rewinding directory {directory:?} ");
175
176        get_context_instance()
177            .perform_operation_on_directory(directory.try_into()?, SynchronousDirectory::rewind)
178            .ok_or(Error::InvalidIdentifier)??;
179
180        Ok(())
181    })
182}
183
184#[unsafe(no_mangle)]
185pub extern "C" fn xila_file_system_directory_set_position(
186    directory: XilaFileIdentifier,
187    offset: XilaFileSystemSize,
188) -> u32 {
189    into_u32(move || {
190        debug!("Setting position in directory {directory:?} to offset {offset}");
191
192        get_context_instance()
193            .perform_operation_on_directory(directory.try_into()?, |d| d.set_position(offset))
194            .ok_or(Error::InvalidIdentifier)??;
195
196        Ok(())
197    })
198}
199
200#[cfg(test)]
201mod tests {
202    extern crate std;
203
204    use super::*;
205    use abi_context::get_instance as get_context_instance;
206    use alloc::{ffi::CString, format, vec::Vec};
207    use file_system::{AccessFlags, CreateFlags, MemoryDevice, PathOwned};
208    use task::{TaskIdentifier, test};
209    use virtual_file_system::{Directory, File, VirtualFileSystem};
210
211    async fn initialize() -> (TaskIdentifier, &'static VirtualFileSystem) {
212        if !log::is_initialized() {
213            log::initialize(&drivers_std::log::Logger).unwrap();
214        }
215
216        let users_manager = users::initialize();
217
218        let time_manager = time::initialize(&drivers_std::devices::TimeDevice).unwrap();
219
220        let task_manager = task::initialize();
221
222        let task = task::get_instance().get_current_task_identifier().await;
223
224        let device = MemoryDevice::<512>::new_static(1024 * 512);
225
226        let cache_size = 256;
227
228        little_fs::FileSystem::format(device, cache_size).unwrap();
229        let file_system = little_fs::FileSystem::new(device, cache_size).unwrap();
230
231        let virtual_file_system =
232            virtual_file_system::initialize(task_manager, users_manager, time_manager, file_system)
233                .unwrap();
234
235        (task, virtual_file_system)
236    }
237
238    #[test]
239    async fn test_null_pointer_handling() {
240        // Test that functions properly handle null pointers and return appropriate error codes
241        let (_task, _vfs) = initialize().await;
242
243        let context = get_context_instance();
244
245        // Test open directory with null path
246        let mut directory_id: XilaFileIdentifier = 0;
247        let result = context
248            .call_abi(async || unsafe {
249                xila_file_system_open_directory(core::ptr::null(), &mut directory_id)
250            })
251            .await;
252        assert_ne!(result, 0, "Opening directory with null path should fail");
253
254        // Test read directory with null output pointers
255        let invalid_handle: XilaFileIdentifier = 9999;
256        let result = context
257            .call_abi(async || unsafe {
258                xila_file_system_read_directory(
259                    invalid_handle,
260                    core::ptr::null_mut(),
261                    core::ptr::null_mut(),
262                    core::ptr::null_mut(),
263                    core::ptr::null_mut(),
264                )
265            })
266            .await;
267        assert_ne!(
268            result, 0,
269            "Reading directory with null pointers should fail"
270        );
271    }
272
273    #[test]
274    async fn test_invalid_handle_operations() {
275        initialize().await; // Ensure the test environment is initialized
276        // Test operations on invalid directory handles
277        let invalid_handle: XilaFileIdentifier = 9999;
278        let context = get_context_instance();
279
280        // Test close with invalid handle
281        let result = context
282            .call_abi(|| async { xila_file_system_close_directory(invalid_handle) })
283            .await;
284        assert_ne!(result, 0, "Closing invalid directory handle should fail");
285
286        // Test rewind with invalid handle
287        let result = context
288            .call_abi(|| async { xila_file_system_rewind_directory(invalid_handle) })
289            .await;
290        assert_ne!(result, 0, "Rewinding invalid directory handle should fail");
291
292        // Test set position with invalid handle
293        let result = context
294            .call_abi(|| async { xila_file_system_directory_set_position(invalid_handle, 0) })
295            .await;
296        assert_ne!(
297            result, 0,
298            "Setting position on invalid directory handle should fail"
299        );
300    }
301
302    #[test]
303    async fn test_read_directory_parameter_validation() {
304        initialize().await; // Ensure the test environment is initialized
305
306        // Test that read directory validates its parameters properly
307        let invalid_handle: XilaFileIdentifier = 0;
308        let mut entry_name: *const c_char = core::ptr::null();
309        let mut entry_type: XilaFileKind = XilaFileKind::File;
310        let mut entry_size: XilaFileSystemSize = 0;
311        let mut entry_inode: XilaFileSystemInode = 0;
312        let context = get_context_instance();
313
314        // Test with invalid handle but valid output pointers
315        let result = context
316            .call_abi(async || unsafe {
317                xila_file_system_read_directory(
318                    invalid_handle,
319                    &mut entry_name,
320                    &mut entry_type,
321                    &mut entry_size,
322                    &mut entry_inode,
323                )
324            })
325            .await;
326        assert_ne!(result, 0, "Reading from invalid handle should fail");
327    }
328
329    #[test]
330    async fn test_set_position_boundary_values() {
331        initialize().await; // Ensure the test environment is initialized
332        // Test set position with boundary values
333        let invalid_handle: XilaFileIdentifier = 9999;
334        let context = get_context_instance();
335
336        // Test with maximum value
337        let result = context
338            .call_abi(|| async {
339                xila_file_system_directory_set_position(invalid_handle, u64::MAX)
340            })
341            .await;
342        assert_ne!(
343            result, 0,
344            "Setting position with max value on invalid handle should fail"
345        );
346
347        // Test with zero value
348        let result = context
349            .call_abi(|| async { xila_file_system_directory_set_position(invalid_handle, 0) })
350            .await;
351        assert_ne!(
352            result, 0,
353            "Setting position with zero on invalid handle should fail"
354        );
355    }
356
357    #[test]
358    async fn test_open_directory_valid_path() {
359        let (_task, _vfs) = initialize().await;
360        let context = get_context_instance();
361
362        let path = CString::new("/").unwrap();
363        let mut directory_id: XilaFileIdentifier = 0;
364
365        let result = context
366            .call_abi(async || unsafe {
367                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
368            })
369            .await;
370        assert_eq!(result, 0, "Opening root directory should succeed");
371        assert_ne!(directory_id, 0, "Directory identifier should be non-zero");
372
373        // Clean up
374        let close_result = context
375            .call_abi(|| async { xila_file_system_close_directory(directory_id) })
376            .await;
377        assert_eq!(close_result, 0, "Closing directory should succeed");
378    }
379
380    #[test]
381    async fn test_open_directory_invalid_path() {
382        let (_task, _vfs) = initialize().await;
383        let context = get_context_instance();
384
385        let path = CString::new("/nonexistent").unwrap();
386        let mut directory_id: XilaFileIdentifier = 0;
387
388        let result = context
389            .call_abi(async || unsafe {
390                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
391            })
392            .await;
393
394        assert_ne!(result, 0, "Opening nonexistent directory should fail");
395    }
396
397    #[test]
398    async fn test_open_directory_null_path() {
399        let (_task, _vfs) = initialize().await;
400        let context = get_context_instance();
401
402        let mut directory_id: XilaFileIdentifier = 0;
403
404        let result = context
405            .call_abi(async || unsafe {
406                xila_file_system_open_directory(core::ptr::null(), &mut directory_id)
407            })
408            .await;
409
410        assert_ne!(result, 0, "Opening directory with null path should fail");
411    }
412
413    #[test]
414    async fn test_read_directory_entries() {
415        let (_task, vfs) = initialize().await;
416        let task = _task;
417        let context = get_context_instance();
418
419        // Create some test files and directories
420        Directory::create(vfs, task, "/test_read_directory_entries")
421            .await
422            .unwrap();
423
424        let test_file = File::open(
425            vfs,
426            task,
427            "/test_read_directory_entries.txt",
428            Flags::new(AccessFlags::Write, Some(CreateFlags::Create), None),
429        )
430        .await
431        .unwrap();
432
433        test_file.close(vfs).await.unwrap();
434
435        // Open root directory
436        let path = CString::new("/").unwrap();
437        let mut directory_id: XilaFileIdentifier = 0;
438
439        let open_result = context
440            .call_abi(async || unsafe {
441                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
442            })
443            .await;
444        assert_eq!(open_result, 0, "Opening root directory should succeed");
445
446        // Read directory entries
447
448        let mut entries_found = Vec::new();
449
450        // Read all entries
451        loop {
452            let mut entry_name: *const c_char = core::ptr::null();
453            let mut entry_type: XilaFileKind = XilaFileKind::File;
454            let mut entry_size: XilaFileSystemSize = 0;
455            let mut entry_inode: XilaFileSystemInode = 0;
456
457            let read_result = context
458                .call_abi(async || unsafe {
459                    xila_file_system_read_directory(
460                        directory_id,
461                        &mut entry_name,
462                        &mut entry_type,
463                        &mut entry_size,
464                        &mut entry_inode,
465                    )
466                })
467                .await;
468
469            assert_eq!(read_result, 0, "Reading directory entry should succeed");
470
471            if entry_name.is_null() {
472                break; // End of directory
473            }
474
475            let name = unsafe { CStr::from_ptr(entry_name).to_string_lossy().into_owned() };
476            entries_found.push((name, entry_type));
477
478            // Free the allocated string
479            unsafe {
480                let _ = CString::from_raw(entry_name as *mut c_char);
481            }
482        }
483
484        // Verify we found the expected entries
485        assert!(
486            entries_found.len() >= 2,
487            "Should find at least current and parent directories"
488        );
489
490        // Check for current and parent directory entries
491        let has_current = entries_found.iter().any(|(name, _)| name == ".");
492        let has_parent = entries_found.iter().any(|(name, _)| name == "..");
493
494        assert!(has_current, "Should find current directory entry");
495        assert!(has_parent, "Should find parent directory entry");
496
497        // Clean up
498        let close_result = context
499            .call_abi(|| async { xila_file_system_close_directory(directory_id) })
500            .await;
501        assert_eq!(close_result, 0, "Closing directory should succeed");
502    }
503
504    #[test]
505    async fn test_read_directory_invalid_handle() {
506        let (_task, _vfs) = initialize().await;
507        let context = get_context_instance();
508
509        let invalid_directory_id: XilaFileIdentifier = 9999;
510        let mut entry_name: *const c_char = core::ptr::null();
511        let mut entry_type: XilaFileKind = XilaFileKind::File;
512        let mut entry_size: XilaFileSystemSize = 0;
513        let mut entry_inode: XilaFileSystemInode = 0;
514
515        let result = context
516            .call_abi(async || unsafe {
517                xila_file_system_read_directory(
518                    invalid_directory_id,
519                    &mut entry_name,
520                    &mut entry_type,
521                    &mut entry_size,
522                    &mut entry_inode,
523                )
524            })
525            .await;
526
527        assert_ne!(
528            result, 0,
529            "Reading from invalid directory handle should fail"
530        );
531    }
532
533    #[test]
534    async fn test_close_directory_valid_handle() {
535        let (_task, _vfs) = initialize().await;
536        let context = get_context_instance();
537
538        let path = CString::new("/").unwrap();
539        let mut directory_id: XilaFileIdentifier = 0;
540
541        // Open directory
542        let open_result = context
543            .call_abi(async || unsafe {
544                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
545            })
546            .await;
547        assert_eq!(open_result, 0, "Opening directory should succeed");
548
549        // Close directory
550        let close_result = context
551            .call_abi(|| async { xila_file_system_close_directory(directory_id) })
552            .await;
553        assert_eq!(close_result, 0, "Closing directory should succeed");
554    }
555
556    #[test]
557    async fn test_close_directory_invalid_handle() {
558        let (_task, _vfs) = initialize().await;
559        let context = get_context_instance();
560
561        let invalid_directory_id: XilaFileIdentifier = 9999;
562
563        let result = context
564            .call_abi(|| async { xila_file_system_close_directory(invalid_directory_id) })
565            .await;
566        assert_ne!(result, 0, "Closing invalid directory handle should fail");
567    }
568
569    #[test]
570    async fn test_rewind_directory() {
571        let (_task, vfs) = initialize().await;
572        let task = _task;
573        let context = get_context_instance();
574
575        // Create some test files
576        for i in 0..3 {
577            let path = format!("/test_rewind_directory_{i}.txt");
578
579            let test_file = vfs
580                .open(
581                    &PathOwned::new(path).unwrap(),
582                    file_system::Flags::new(AccessFlags::Write, Some(CreateFlags::Create), None),
583                    task,
584                )
585                .await
586                .unwrap();
587            test_file.close(vfs).await.unwrap();
588        }
589
590        // Open directory
591        let path = CString::new("/").unwrap();
592        let mut directory_id: XilaFileIdentifier = 0;
593
594        let open_result = context
595            .call_abi(async || unsafe {
596                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
597            })
598            .await;
599        assert_eq!(open_result, 0, "Opening directory should succeed");
600
601        // Read a few entries
602        for _ in 0..2 {
603            let mut entry_name: *const c_char = core::ptr::null();
604            let mut entry_type: XilaFileKind = XilaFileKind::File;
605            let mut entry_size: XilaFileSystemSize = 0;
606            let mut entry_inode: XilaFileSystemInode = 0;
607
608            let read_result = context
609                .call_abi(async || unsafe {
610                    xila_file_system_read_directory(
611                        directory_id,
612                        &mut entry_name,
613                        &mut entry_type,
614                        &mut entry_size,
615                        &mut entry_inode,
616                    )
617                })
618                .await;
619            assert_eq!(read_result, 0, "Reading directory should succeed");
620
621            if !entry_name.is_null() {
622                unsafe {
623                    let _ = CString::from_raw(entry_name as *mut c_char);
624                }
625            }
626        }
627
628        // Rewind directory
629        let rewind_result = context
630            .call_abi(|| async { xila_file_system_rewind_directory(directory_id) })
631            .await;
632        assert_eq!(rewind_result, 0, "Rewinding directory should succeed");
633
634        // Read first entry again - should be "."
635        let mut entry_name: *const c_char = core::ptr::null();
636        let mut entry_type: XilaFileKind = XilaFileKind::File;
637        let mut entry_size: XilaFileSystemSize = 0;
638        let mut entry_inode: XilaFileSystemInode = 0;
639
640        let read_result = context
641            .call_abi(async || unsafe {
642                xila_file_system_read_directory(
643                    directory_id,
644                    &mut entry_name,
645                    &mut entry_type,
646                    &mut entry_size,
647                    &mut entry_inode,
648                )
649            })
650            .await;
651        assert_eq!(
652            read_result, 0,
653            "Reading directory after rewind should succeed"
654        );
655        assert!(!entry_name.is_null(), "Entry name should not be null");
656
657        let name = unsafe { CStr::from_ptr(entry_name).to_string_lossy() };
658        assert_eq!(
659            name, ".",
660            "First entry after rewind should be current directory"
661        );
662
663        unsafe {
664            let _ = CString::from_raw(entry_name as *mut c_char);
665        }
666
667        // Clean up
668        let close_result = context
669            .call_abi(|| async { xila_file_system_close_directory(directory_id) })
670            .await;
671        assert_eq!(close_result, 0, "Closing directory should succeed");
672    }
673
674    #[test]
675    async fn test_rewind_directory_invalid_handle() {
676        let (_task, _vfs) = initialize().await;
677        let context = get_context_instance();
678
679        let invalid_directory_id: XilaFileIdentifier = 9999;
680
681        let result = context
682            .call_abi(|| async { xila_file_system_rewind_directory(invalid_directory_id) })
683            .await;
684        assert_ne!(result, 0, "Rewinding invalid directory handle should fail");
685    }
686
687    #[test]
688    async fn test_directory_set_position() {
689        let (_task, vfs) = initialize().await;
690        let task = _task;
691        let context = get_context_instance();
692
693        // Create some test files
694        for i in 0..5 {
695            let path = format!("/test_file_{i}.txt");
696
697            let test_file = vfs
698                .open(
699                    &PathOwned::new(path).unwrap(),
700                    file_system::Flags::new(AccessFlags::Write, Some(CreateFlags::Create), None),
701                    task,
702                )
703                .await
704                .unwrap();
705            test_file.close(vfs).await.unwrap();
706        }
707
708        // Open directory
709        let path = CString::new("/").unwrap();
710        let mut directory_id: XilaFileIdentifier = 0;
711
712        let open_result = context
713            .call_abi(async || unsafe {
714                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
715            })
716            .await;
717        assert_eq!(open_result, 0, "Opening directory should succeed");
718
719        // Read a few entries to advance position
720        for _ in 0..3 {
721            let mut entry_name: *const c_char = core::ptr::null();
722            let mut entry_type: XilaFileKind = XilaFileKind::File;
723            let mut entry_size: XilaFileSystemSize = 0;
724            let mut entry_inode: XilaFileSystemInode = 0;
725
726            let read_result = context
727                .call_abi(async || unsafe {
728                    xila_file_system_read_directory(
729                        directory_id,
730                        &mut entry_name,
731                        &mut entry_type,
732                        &mut entry_size,
733                        &mut entry_inode,
734                    )
735                })
736                .await;
737            assert_eq!(read_result, 0, "Reading directory should succeed");
738
739            if !entry_name.is_null() {
740                unsafe {
741                    let _ = CString::from_raw(entry_name as *mut c_char);
742                }
743            }
744        }
745
746        // Set position back to start
747        let set_position_result = context
748            .call_abi(|| async { xila_file_system_directory_set_position(directory_id, 0) })
749            .await;
750        assert_eq!(
751            set_position_result, 0,
752            "Setting directory position should succeed"
753        );
754
755        // Read first entry - should be "." again
756        let mut entry_name: *const c_char = core::ptr::null();
757        let mut entry_type: XilaFileKind = XilaFileKind::File;
758        let mut entry_size: XilaFileSystemSize = 0;
759        let mut entry_inode: XilaFileSystemInode = 0;
760
761        let read_result = context
762            .call_abi(async || unsafe {
763                xila_file_system_read_directory(
764                    directory_id,
765                    &mut entry_name,
766                    &mut entry_type,
767                    &mut entry_size,
768                    &mut entry_inode,
769                )
770            })
771            .await;
772        assert_eq!(
773            read_result, 0,
774            "Reading directory after position reset should succeed"
775        );
776        assert!(!entry_name.is_null(), "Entry name should not be null");
777
778        let name = unsafe { CStr::from_ptr(entry_name).to_string_lossy() };
779        assert_eq!(
780            name, ".",
781            "First entry after position reset should be current directory"
782        );
783
784        unsafe {
785            let _ = CString::from_raw(entry_name as *mut c_char);
786        }
787
788        // Clean up
789        let close_result = context
790            .call_abi(|| async { xila_file_system_close_directory(directory_id) })
791            .await;
792        assert_eq!(close_result, 0, "Closing directory should succeed");
793    }
794
795    #[test]
796    async fn test_directory_set_position_invalid_handle() {
797        let (_task, _vfs) = initialize().await;
798        let context = get_context_instance();
799
800        let invalid_directory_id: XilaFileIdentifier = 9999;
801
802        let result = context
803            .call_abi(|| async { xila_file_system_directory_set_position(invalid_directory_id, 0) })
804            .await;
805        assert_ne!(
806            result, 0,
807            "Setting position on invalid directory handle should fail"
808        );
809    }
810
811    #[test]
812    async fn test_directory_operations_sequence() {
813        let (_task, vfs) = initialize().await;
814        let task = _task;
815        let context = get_context_instance();
816
817        // Create test structure
818        Directory::create(vfs, task, "/test_dir").await.unwrap();
819        Directory::create(vfs, task, "/test_dir/subdir")
820            .await
821            .unwrap();
822
823        let test_file = File::open(
824            vfs,
825            task,
826            &"/test_dir/file.txt",
827            file_system::Flags::new(AccessFlags::Write, Some(CreateFlags::Create), None),
828        )
829        .await
830        .unwrap();
831        test_file.close(vfs).await.unwrap();
832
833        // Test opening the created directory
834        let path = CString::new("/test_dir").unwrap();
835        let mut directory_id: XilaFileIdentifier = 0;
836
837        let open_result = context
838            .call_abi(async || unsafe {
839                xila_file_system_open_directory(path.as_ptr(), &mut directory_id)
840            })
841            .await;
842        assert_eq!(open_result, 0, "Opening test directory should succeed");
843
844        // Count entries
845        let mut entry_count = 0;
846        loop {
847            let mut entry_name: *const c_char = core::ptr::null();
848            let mut entry_type: XilaFileKind = XilaFileKind::File;
849            let mut entry_size: XilaFileSystemSize = 0;
850            let mut entry_inode: XilaFileSystemInode = 0;
851
852            let read_result = context
853                .call_abi(async || unsafe {
854                    xila_file_system_read_directory(
855                        directory_id,
856                        &mut entry_name,
857                        &mut entry_type,
858                        &mut entry_size,
859                        &mut entry_inode,
860                    )
861                })
862                .await;
863            assert_eq!(read_result, 0, "Reading directory should succeed");
864
865            if entry_name.is_null() {
866                break;
867            }
868
869            entry_count += 1;
870            unsafe {
871                let _ = CString::from_raw(entry_name as *mut c_char);
872            }
873        }
874
875        // Should have at least: ., .., subdir, file.txt
876        assert!(
877            entry_count >= 4,
878            "Should find at least 4 entries in test directory"
879        );
880
881        // Test rewind and count again
882        let rewind_result = context
883            .call_abi(|| async { xila_file_system_rewind_directory(directory_id) })
884            .await;
885        assert_eq!(rewind_result, 0, "Rewinding directory should succeed");
886
887        let mut rewind_count = 0;
888        loop {
889            let mut entry_name: *const c_char = core::ptr::null();
890            let mut entry_type: XilaFileKind = XilaFileKind::File;
891            let mut entry_size: XilaFileSystemSize = 0;
892            let mut entry_inode: XilaFileSystemInode = 0;
893
894            let read_result = context
895                .call_abi(async || unsafe {
896                    xila_file_system_read_directory(
897                        directory_id,
898                        &mut entry_name,
899                        &mut entry_type,
900                        &mut entry_size,
901                        &mut entry_inode,
902                    )
903                })
904                .await;
905            assert_eq!(
906                read_result, 0,
907                "Reading directory after rewind should succeed"
908            );
909
910            if entry_name.is_null() {
911                break;
912            }
913
914            rewind_count += 1;
915            unsafe {
916                let _ = CString::from_raw(entry_name as *mut c_char);
917            }
918        }
919
920        assert_eq!(
921            entry_count, rewind_count,
922            "Entry count should be same after rewind"
923        );
924
925        // Clean up
926        let close_result = context
927            .call_abi(|| async { xila_file_system_close_directory(directory_id) })
928            .await;
929        assert_eq!(close_result, 0, "Closing directory should succeed");
930    }
931
932    #[test]
933    async fn test_directory_operations_error_handling() {
934        initialize().await;
935
936        let context = get_context_instance();
937
938        // Test null pointer handling
939        let mut directory_id: XilaFileIdentifier = 0;
940
941        // Test with null path
942        let result = context
943            .call_abi(async || unsafe {
944                xila_file_system_open_directory(core::ptr::null(), &mut directory_id)
945            })
946            .await;
947        assert_ne!(result, 0, "Null path should cause error");
948
949        // Test invalid operations on invalid handles
950        let invalid_handle = 0;
951
952        let close_result = context
953            .call_abi(|| async { xila_file_system_close_directory(invalid_handle) })
954            .await;
955        assert_ne!(close_result, 0, "Invalid close should fail");
956
957        let rewind_result = context
958            .call_abi(|| async { xila_file_system_rewind_directory(invalid_handle) })
959            .await;
960        assert_ne!(rewind_result, 0, "Invalid rewind should fail");
961
962        let set_pos_result = context
963            .call_abi(|| async { xila_file_system_directory_set_position(invalid_handle, 0) })
964            .await;
965        assert_ne!(set_pos_result, 0, "Invalid set position should fail");
966
967        let mut entry_name: *const c_char = core::ptr::null();
968        let mut entry_type: XilaFileKind = XilaFileKind::File;
969        let mut entry_size: XilaFileSystemSize = 0;
970        let mut entry_inode: XilaFileSystemInode = 0;
971
972        let read_result = context
973            .call_abi(async || unsafe {
974                xila_file_system_read_directory(
975                    invalid_handle,
976                    &mut entry_name,
977                    &mut entry_type,
978                    &mut entry_size,
979                    &mut entry_inode,
980                )
981            })
982            .await;
983        assert_ne!(read_result, 0, "Invalid read should fail");
984    }
985}