Skip to main content

strat9_kernel/vfs/
scheme.rs

1//! Scheme abstraction - backends for VFS operations.
2//!
3//! Schemes provide the actual implementation for file operations.
4//! Examples: IPC-based schemes (ext4, network), kernel schemes (devfs, procfs).
5
6use crate::{
7    ipc::{message::IpcMessage, port::PortId},
8    sync::SpinLock,
9    syscall::error::SyscallError,
10};
11use alloc::{
12    collections::BTreeMap,
13    string::{String, ToString},
14    sync::Arc,
15    vec::Vec,
16};
17
18pub use strat9_abi::data::{
19    FileStat, DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK, DT_UNKNOWN,
20};
21
22/// A single directory entry returned by readdir.
23#[derive(Debug, Clone)]
24pub struct DirEntry {
25    pub ino: u64,
26    pub file_type: u8,
27    pub name: String,
28}
29
30/// Result of an open operation.
31#[derive(Debug, Clone)]
32pub struct OpenResult {
33    /// Unique file handle (opaque to caller).
34    pub file_id: u64,
35    /// Size of the file (if known).
36    pub size: Option<u64>,
37    /// Flags describing the file (directory, device, etc.).
38    pub flags: FileFlags,
39}
40
41bitflags::bitflags! {
42    /// Flags describing a file's properties.
43    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
44    pub struct FileFlags: u32 {
45        const DIRECTORY = 1 << 0;
46        const DEVICE    = 1 << 1;
47        const PIPE      = 1 << 2;
48        const APPEND    = 1 << 3;
49    }
50}
51
52pub use strat9_abi::flag::OpenFlags;
53
54/// Abstraction for a filesystem/service backend.
55pub trait Scheme: Send + Sync {
56    /// Open a file/resource at the given path within this scheme.
57    ///
58    /// `path` is relative to the scheme's mount point.
59    /// Returns a unique file handle + metadata.
60    fn open(&self, path: &str, flags: OpenFlags) -> Result<OpenResult, SyscallError>;
61
62    /// Read bytes from an open file.
63    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError>;
64
65    /// Write bytes to an open file.
66    fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError>;
67
68    /// Close an open file.
69    fn close(&self, file_id: u64) -> Result<(), SyscallError>;
70
71    /// Get file size (if supported).
72    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
73        let _ = file_id;
74        Err(SyscallError::NotImplemented)
75    }
76
77    /// Truncate/resize a file (if supported).
78    fn truncate(&self, file_id: u64, new_size: u64) -> Result<(), SyscallError> {
79        let _ = (file_id, new_size);
80        Err(SyscallError::NotImplemented)
81    }
82
83    /// Sync file to storage (if applicable).
84    fn sync(&self, file_id: u64) -> Result<(), SyscallError> {
85        let _ = file_id;
86        Ok(()) // No-op by default
87    }
88
89    /// Create a new regular file.
90    fn create_file(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
91        let _ = (path, mode);
92        Err(SyscallError::NotImplemented)
93    }
94
95    /// Create a new directory.
96    fn create_directory(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
97        let _ = (path, mode);
98        Err(SyscallError::NotImplemented)
99    }
100
101    /// Remove a file or directory.
102    fn unlink(&self, path: &str) -> Result<(), SyscallError> {
103        let _ = path;
104        Err(SyscallError::NotImplemented)
105    }
106
107    /// Get metadata for an open file.
108    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
109        let _ = file_id;
110        Err(SyscallError::NotImplemented)
111    }
112
113    /// Read directory entries from an open directory handle.
114    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
115        let _ = file_id;
116        Err(SyscallError::NotImplemented)
117    }
118
119    /// Rename/move an entry within this scheme.
120    fn rename(&self, old_path: &str, new_path: &str) -> Result<(), SyscallError> {
121        let _ = (old_path, new_path);
122        Err(SyscallError::NotImplemented)
123    }
124
125    /// Change permission bits on a path.
126    fn chmod(&self, path: &str, mode: u32) -> Result<(), SyscallError> {
127        let _ = (path, mode);
128        Err(SyscallError::NotImplemented)
129    }
130
131    /// Change permission bits on an open file handle.
132    fn fchmod(&self, file_id: u64, mode: u32) -> Result<(), SyscallError> {
133        let _ = (file_id, mode);
134        Err(SyscallError::NotImplemented)
135    }
136
137    /// Create a hard link.
138    fn link(&self, old_path: &str, new_path: &str) -> Result<(), SyscallError> {
139        let _ = (old_path, new_path);
140        Err(SyscallError::NotImplemented)
141    }
142
143    /// Create a symbolic link.
144    fn symlink(&self, target: &str, link_path: &str) -> Result<(), SyscallError> {
145        let _ = (target, link_path);
146        Err(SyscallError::NotImplemented)
147    }
148
149    /// Read the target of a symbolic link.
150    fn readlink(&self, path: &str) -> Result<String, SyscallError> {
151        let _ = path;
152        Err(SyscallError::NotImplemented)
153    }
154}
155
156/// Type-erased Scheme reference.
157pub type DynScheme = Arc<dyn Scheme>;
158
159pub const DEV_RAMFS: u64 = 1;
160pub const DEV_SYSFS: u64 = 2;
161pub const DEV_PROCFS: u64 = 3;
162pub const DEV_DEVFS: u64 = 4;
163pub const DEV_CONSOLE: u64 = 5;
164pub const DEV_PIPEFS: u64 = 6;
165pub const DEV_IPCFS: u64 = 7;
166pub const DEV_NETFS: u64 = 8;
167
168/// Finalize pseudo-filesystem stats with a stable device identity and
169/// synthetic timestamps.
170pub fn finalize_pseudo_stat(mut st: FileStat, st_dev: u64, st_rdev: u64) -> FileStat {
171    let now = strat9_abi::data::TimeSpec::from_nanos(crate::syscall::time::current_time_ns());
172    st.st_dev = st_dev;
173    st.st_rdev = st_rdev;
174    st.st_atime = now;
175    st.st_mtime = now;
176    st.st_ctime = now;
177    st
178}
179
180// ============================================================================
181// Built-in Schemes
182// ============================================================================
183
184/// IPC-based scheme: forwards operations to a userspace server via IPC.
185pub struct IpcScheme {
186    port_id: PortId,
187}
188
189impl IpcScheme {
190    /// Creates a new instance.
191    pub fn new(port_id: PortId) -> Self {
192        IpcScheme { port_id }
193    }
194
195    /// Build an IPC message for open operation.
196    fn build_open_msg(path: &str, flags: OpenFlags) -> Result<IpcMessage, SyscallError> {
197        const OPCODE_OPEN: u32 = 0x01;
198        let mut msg = IpcMessage::new(OPCODE_OPEN);
199
200        // Encode: [flags: u32][path_len: u16][path bytes...]
201        if path.len() > 42 {
202            return Err(SyscallError::InvalidArgument); // Path too long for inline
203        }
204
205        msg.payload[0..4].copy_from_slice(&flags.bits().to_le_bytes());
206        msg.payload[4..6].copy_from_slice(&(path.len() as u16).to_le_bytes());
207        msg.payload[6..6 + path.len()].copy_from_slice(path.as_bytes());
208        Ok(msg)
209    }
210
211    /// Build an IPC message for read operation.
212    fn build_read_msg(file_id: u64, offset: u64, count: u32) -> IpcMessage {
213        const OPCODE_READ: u32 = 0x02;
214        let mut msg = IpcMessage::new(OPCODE_READ);
215        msg.payload[0..8].copy_from_slice(&file_id.to_le_bytes());
216        msg.payload[8..16].copy_from_slice(&offset.to_le_bytes());
217        msg.payload[16..20].copy_from_slice(&count.to_le_bytes());
218        msg
219    }
220
221    /// Build an IPC message for write operation.
222    ///
223    /// Returns the message and the number of bytes actually packed.
224    fn build_write_msg(file_id: u64, offset: u64, data: &[u8]) -> (IpcMessage, usize) {
225        const OPCODE_WRITE: u32 = 0x03;
226        let mut msg = IpcMessage::new(OPCODE_WRITE);
227        msg.payload[0..8].copy_from_slice(&file_id.to_le_bytes());
228        msg.payload[8..16].copy_from_slice(&offset.to_le_bytes());
229
230        // payload[18..48] leaves 30 bytes for data.
231        let packed = core::cmp::min(data.len(), 30);
232        msg.payload[16..18].copy_from_slice(&(packed as u16).to_le_bytes());
233        msg.payload[18..18 + packed].copy_from_slice(&data[..packed]);
234        (msg, packed)
235    }
236
237    /// Build an IPC message for close operation.
238    fn build_close_msg(file_id: u64) -> IpcMessage {
239        const OPCODE_CLOSE: u32 = 0x04;
240        let mut msg = IpcMessage::new(OPCODE_CLOSE);
241        msg.payload[0..8].copy_from_slice(&file_id.to_le_bytes());
242        msg
243    }
244
245    /// Performs the build readdir msg operation.
246    fn build_readdir_msg(file_id: u64, cursor: u16) -> IpcMessage {
247        const OPCODE_READDIR: u32 = 0x08;
248        let mut msg = IpcMessage::new(OPCODE_READDIR);
249        msg.payload[0..8].copy_from_slice(&file_id.to_le_bytes());
250        msg.payload[8..10].copy_from_slice(&cursor.to_le_bytes());
251        msg
252    }
253
254    /// Parses status.
255    fn parse_status(reply: &IpcMessage) -> Result<(), SyscallError> {
256        if reply.msg_type != 0x80 {
257            return Err(SyscallError::IoError);
258        }
259
260        let status = u32::from_le_bytes([
261            reply.payload[0],
262            reply.payload[1],
263            reply.payload[2],
264            reply.payload[3],
265        ]);
266        if status == 0 {
267            return Ok(());
268        }
269
270        // Accept both forms:
271        // - positive errno (2 => ENOENT)
272        // - raw signed -errno encoded in u32
273        let signed = status as i32;
274        let code = if signed < 0 {
275            signed as i64
276        } else {
277            -(signed as i64)
278        };
279        Err(SyscallError::from_code(code))
280    }
281}
282
283impl IpcScheme {
284    /// Perform a synchronous IPC call: send `msg` to the server port and block
285    /// the current task until the server calls `ipc_reply`.  This mirrors
286    /// `sys_ipc_call` exactly so that `sys_ipc_reply` can correctly route the
287    /// reply back to us via `reply::deliver_reply`.
288    fn call(&self, mut msg: IpcMessage) -> Result<IpcMessage, SyscallError> {
289        let task_id = crate::process::current_task_id().ok_or(SyscallError::PermissionDenied)?;
290
291        // Stamp our task-id so the server knows where to deliver the reply.
292        msg.sender = task_id.as_u64();
293
294        let port = crate::ipc::port::get_port(self.port_id).ok_or(SyscallError::BadHandle)?;
295        port.send(msg).map_err(|_| SyscallError::BadHandle)?;
296        // Drop the Arc before blocking so we don't hold the port alive across
297        // a potentially long sleep.
298        drop(port);
299
300        Ok(crate::ipc::reply::wait_for_reply(task_id))
301    }
302}
303
304impl Scheme for IpcScheme {
305    /// Performs the open operation.
306    fn open(&self, path: &str, flags: OpenFlags) -> Result<OpenResult, SyscallError> {
307        let msg = Self::build_open_msg(path, flags)?;
308        let reply = self.call(msg)?;
309
310        // Parse reply: [status: u32][file_id: u64][size: u64][flags: u32]
311        Self::parse_status(&reply)?;
312
313        let file_id = u64::from_le_bytes([
314            reply.payload[4],
315            reply.payload[5],
316            reply.payload[6],
317            reply.payload[7],
318            reply.payload[8],
319            reply.payload[9],
320            reply.payload[10],
321            reply.payload[11],
322        ]);
323
324        let size = u64::from_le_bytes([
325            reply.payload[12],
326            reply.payload[13],
327            reply.payload[14],
328            reply.payload[15],
329            reply.payload[16],
330            reply.payload[17],
331            reply.payload[18],
332            reply.payload[19],
333        ]);
334
335        let file_flags = u32::from_le_bytes([
336            reply.payload[20],
337            reply.payload[21],
338            reply.payload[22],
339            reply.payload[23],
340        ]);
341
342        Ok(OpenResult {
343            file_id,
344            size: if size == u64::MAX { None } else { Some(size) },
345            flags: FileFlags::from_bits_truncate(file_flags),
346        })
347    }
348
349    /// Performs the read operation.
350    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
351        let msg = Self::build_read_msg(file_id, offset, buf.len() as u32);
352        let reply = self.call(msg)?;
353
354        // Parse reply: [status: u32][bytes_read: u32][data...]
355        Self::parse_status(&reply)?;
356
357        let bytes_read = u32::from_le_bytes([
358            reply.payload[4],
359            reply.payload[5],
360            reply.payload[6],
361            reply.payload[7],
362        ]) as usize;
363
364        let available = core::cmp::min(bytes_read, reply.payload.len() - 8);
365        let to_copy = core::cmp::min(available, buf.len());
366        buf[..to_copy].copy_from_slice(&reply.payload[8..8 + to_copy]);
367
368        Ok(to_copy)
369    }
370
371    /// Performs the write operation.
372    fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
373        let (msg, packed) = Self::build_write_msg(file_id, offset, buf);
374        let reply = self.call(msg)?;
375
376        // Parse reply: [status: u32][bytes_written: u32]
377        Self::parse_status(&reply)?;
378
379        let bytes_written = u32::from_le_bytes([
380            reply.payload[4],
381            reply.payload[5],
382            reply.payload[6],
383            reply.payload[7],
384        ]) as usize;
385
386        // Never report more bytes than we actually sent.
387        Ok(bytes_written.min(packed))
388    }
389
390    /// Performs the close operation.
391    fn close(&self, file_id: u64) -> Result<(), SyscallError> {
392        let msg = Self::build_close_msg(file_id);
393        let reply = self.call(msg)?;
394
395        Self::parse_status(&reply)?;
396
397        Ok(())
398    }
399
400    /// Creates file.
401    fn create_file(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
402        const OPCODE_CREATE_FILE: u32 = 0x05;
403        self.handle_create_op(OPCODE_CREATE_FILE, path, mode)
404    }
405
406    /// Creates directory.
407    fn create_directory(&self, path: &str, mode: u32) -> Result<OpenResult, SyscallError> {
408        const OPCODE_CREATE_DIR: u32 = 0x06;
409        self.handle_create_op(OPCODE_CREATE_DIR, path, mode)
410    }
411
412    /// Performs the unlink operation.
413    fn unlink(&self, path: &str) -> Result<(), SyscallError> {
414        const OPCODE_UNLINK: u32 = 0x07;
415        let mut msg = IpcMessage::new(OPCODE_UNLINK);
416
417        if path.len() > 42 {
418            return Err(SyscallError::InvalidArgument);
419        }
420
421        msg.payload[0..2].copy_from_slice(&(path.len() as u16).to_le_bytes());
422        msg.payload[2..2 + path.len()].copy_from_slice(path.as_bytes());
423
424        let reply = self.call(msg)?;
425        Self::parse_status(&reply)?;
426
427        Ok(())
428    }
429
430    /// Performs the readdir operation.
431    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
432        let mut cursor: u16 = 0;
433        let mut entries = Vec::new();
434
435        loop {
436            let msg = Self::build_readdir_msg(file_id, cursor);
437            let reply = self.call(msg)?;
438            Self::parse_status(&reply)?;
439
440            let next_cursor = u16::from_le_bytes([reply.payload[4], reply.payload[5]]);
441            let entry_count = reply.payload[6] as usize;
442            let used_bytes = reply.payload[7] as usize;
443            if used_bytes > reply.payload.len() - 8 {
444                return Err(SyscallError::IoError);
445            }
446
447            let mut offset = 8usize;
448            for _ in 0..entry_count {
449                if offset + 10 > 8 + used_bytes {
450                    return Err(SyscallError::IoError);
451                }
452
453                let ino = u64::from_le_bytes([
454                    reply.payload[offset],
455                    reply.payload[offset + 1],
456                    reply.payload[offset + 2],
457                    reply.payload[offset + 3],
458                    reply.payload[offset + 4],
459                    reply.payload[offset + 5],
460                    reply.payload[offset + 6],
461                    reply.payload[offset + 7],
462                ]);
463                let file_type = reply.payload[offset + 8];
464                let name_len = reply.payload[offset + 9] as usize;
465                if offset + 10 + name_len > 8 + used_bytes {
466                    return Err(SyscallError::IoError);
467                }
468                let name_bytes = &reply.payload[offset + 10..offset + 10 + name_len];
469                let name = core::str::from_utf8(name_bytes)
470                    .map_err(|_| SyscallError::IoError)?
471                    .to_string();
472
473                entries.push(DirEntry {
474                    ino,
475                    file_type,
476                    name,
477                });
478                offset += 10 + name_len;
479            }
480
481            if next_cursor == u16::MAX {
482                break;
483            }
484            if next_cursor <= cursor {
485                return Err(SyscallError::IoError);
486            }
487            cursor = next_cursor;
488        }
489
490        Ok(entries)
491    }
492}
493
494impl IpcScheme {
495    /// Handles create op.
496    fn handle_create_op(
497        &self,
498        opcode: u32,
499        path: &str,
500        mode: u32,
501    ) -> Result<OpenResult, SyscallError> {
502        let mut msg = IpcMessage::new(opcode);
503
504        if path.len() > 40 {
505            return Err(SyscallError::InvalidArgument);
506        }
507
508        msg.payload[0..4].copy_from_slice(&mode.to_le_bytes());
509        msg.payload[4..6].copy_from_slice(&(path.len() as u16).to_le_bytes());
510        msg.payload[6..6 + path.len()].copy_from_slice(path.as_bytes());
511
512        let reply = self.call(msg)?;
513
514        Self::parse_status(&reply)?;
515
516        let file_id = u64::from_le_bytes([
517            reply.payload[4],
518            reply.payload[5],
519            reply.payload[6],
520            reply.payload[7],
521            reply.payload[8],
522            reply.payload[9],
523            reply.payload[10],
524            reply.payload[11],
525        ]);
526
527        Ok(OpenResult {
528            file_id,
529            size: Some(0),
530            flags: FileFlags::empty(),
531        })
532    }
533}
534
535/// Kernel-backed scheme: serves files from kernel memory (read-only).
536pub struct KernelScheme {
537    files: SpinLock<BTreeMap<String, KernelFile>>,
538    by_id: SpinLock<BTreeMap<u64, String>>,
539}
540
541#[derive(Clone)]
542struct KernelFile {
543    id: u64,
544    base: *const u8,
545    len: usize,
546}
547
548// SAFETY: KernelFile only stores kernel-static pointers
549unsafe impl Send for KernelFile {}
550unsafe impl Sync for KernelFile {}
551
552impl KernelScheme {
553    /// Creates a new instance.
554    pub fn new() -> Self {
555        KernelScheme {
556            files: SpinLock::new(BTreeMap::new()),
557            by_id: SpinLock::new(BTreeMap::new()),
558        }
559    }
560
561    /// Register a static kernel file.
562    pub fn register(&self, path: &str, base: *const u8, len: usize) {
563        static NEXT_ID: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(1);
564        let id = NEXT_ID.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
565        self.files
566            .lock()
567            .insert(String::from(path), KernelFile { id, base, len });
568        self.by_id.lock().insert(id, String::from(path));
569    }
570
571    /// Returns by id.
572    fn get_by_id(&self, file_id: u64) -> Option<KernelFile> {
573        let name = self.by_id.lock().get(&file_id)?.clone();
574        self.files.lock().get(&name).cloned()
575    }
576}
577
578impl Scheme for KernelScheme {
579    /// Performs the open operation.
580    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
581        if path.is_empty() || path == "/" {
582            return Ok(OpenResult {
583                file_id: 0, // Root directory ID
584                size: None,
585                flags: FileFlags::DIRECTORY,
586            });
587        }
588
589        let files = self.files.lock();
590        let file = files.get(path).ok_or(SyscallError::BadHandle)?;
591        Ok(OpenResult {
592            file_id: file.id,
593            size: Some(file.len as u64),
594            flags: FileFlags::empty(),
595        })
596    }
597
598    /// Performs the read operation.
599    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
600        if file_id == 0 {
601            // Handle directory listing for root
602            let mut list = String::new();
603            let files = self.files.lock();
604            for name in files.keys() {
605                list.push_str(name);
606                list.push('\n');
607            }
608
609            if offset >= list.len() as u64 {
610                return Ok(0);
611            }
612
613            let start = offset as usize;
614            let end = core::cmp::min(start + buf.len(), list.len());
615            let to_copy = end - start;
616            buf[..to_copy].copy_from_slice(&list.as_bytes()[start..end]);
617            return Ok(to_copy);
618        }
619
620        let file = self.get_by_id(file_id).ok_or(SyscallError::BadHandle)?;
621
622        if offset >= file.len as u64 {
623            return Ok(0);
624        }
625
626        let remaining = file.len - offset as usize;
627        let to_copy = core::cmp::min(remaining, buf.len());
628
629        // SAFETY: file.base is a kernel-static pointer, bounds checked above
630        unsafe {
631            let src = file.base.add(offset as usize);
632            core::ptr::copy_nonoverlapping(src, buf.as_mut_ptr(), to_copy);
633        }
634
635        Ok(to_copy)
636    }
637
638    /// Performs the write operation.
639    fn write(&self, _file_id: u64, _offset: u64, _buf: &[u8]) -> Result<usize, SyscallError> {
640        Err(SyscallError::PermissionDenied) // Read-only
641    }
642
643    /// Performs the close operation.
644    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
645        Ok(()) // No-op for kernel files
646    }
647
648    /// Performs the size operation.
649    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
650        let file = self.get_by_id(file_id).ok_or(SyscallError::BadHandle)?;
651        Ok(file.len as u64)
652    }
653
654    /// Performs the stat operation.
655    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
656        if file_id == 0 {
657            return Ok(finalize_pseudo_stat(
658                FileStat {
659                    st_ino: 0,
660                    st_mode: 0o040555,
661                    st_nlink: 2,
662                    st_size: 0,
663                    st_blksize: 512,
664                    st_blocks: 0,
665                    ..FileStat::zeroed()
666                },
667                DEV_SYSFS,
668                0,
669            ));
670        }
671        let file = self.get_by_id(file_id).ok_or(SyscallError::BadHandle)?;
672        Ok(finalize_pseudo_stat(
673            FileStat {
674                st_ino: file_id,
675                st_mode: 0o100444,
676                st_nlink: 1,
677                st_size: file.len as u64,
678                st_blksize: 512,
679                st_blocks: ((file.len as u64) + 511) / 512,
680                ..FileStat::zeroed()
681            },
682            DEV_SYSFS,
683            0,
684        ))
685    }
686
687    /// Performs the readdir operation.
688    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
689        if file_id != 0 {
690            return Err(SyscallError::InvalidArgument);
691        }
692        let files = self.files.lock();
693        let mut entries = Vec::new();
694        for (name, kf) in files.iter() {
695            entries.push(DirEntry {
696                ino: kf.id,
697                file_type: DT_REG,
698                name: name.clone(),
699            });
700        }
701        Ok(entries)
702    }
703}