1use crate::cfe::time::SysTime;
8use crate::error::{CfsError, OsalError, Result};
9use crate::ffi;
10use crate::os::id::OsalId;
11use crate::os::util::c_path_from_str;
12use crate::status::{check, Status};
13use bitflags::bitflags;
14use core::ffi::CStr;
15use core::future::Future;
16use core::mem::MaybeUninit;
17use core::ops::Drop;
18use core::task::Poll;
19use heapless::CString;
20
21#[cfg(not(nos3_cfe))]
22use crate::os::time::OsTime;
23
24bitflags! {
25 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
27 pub struct FileMode: u32 {
28 const READ = ffi::OS_FILESTAT_MODE_READ;
30 const WRITE = ffi::OS_FILESTAT_MODE_WRITE;
32 const EXEC = ffi::OS_FILESTAT_MODE_EXEC;
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(u32)]
42pub enum FileCategory {
43 Unknown = ffi::CFE_FS_FileCategory_t_CFE_FS_FileCategory_UNKNOWN,
45 DynamicModule = ffi::CFE_FS_FileCategory_t_CFE_FS_FileCategory_DYNAMIC_MODULE,
47 BinaryDataDump = ffi::CFE_FS_FileCategory_t_CFE_FS_FileCategory_BINARY_DATA_DUMP,
49 TextLog = ffi::CFE_FS_FileCategory_t_CFE_FS_FileCategory_TEXT_LOG,
51 Script = ffi::CFE_FS_FileCategory_t_CFE_FS_FileCategory_SCRIPT,
53 Temp = ffi::CFE_FS_FileCategory_t_CFE_FS_FileCategory_TEMP,
55}
56
57pub struct FileWriteMetaData(pub(crate) ffi::CFE_FS_FileWriteMetaData_t);
62
63#[derive(Debug, Clone, Copy)]
65pub struct FileStat {
66 inner: ffi::os_fstat_t,
67}
68
69impl FileStat {
70 pub fn size(&self) -> usize {
72 self.inner.FileSize
73 }
74
75 pub fn is_dir(&self) -> bool {
77 (self.inner.FileModeBits & ffi::OS_FILESTAT_MODE_DIR) != 0
78 }
79}
80
81#[derive(Debug, Clone)]
83#[repr(transparent)]
84pub struct FsHeader(pub(crate) ffi::CFE_FS_Header_t);
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub struct FsInfo {
89 pub max_fds: u32,
91 pub free_fds: u32,
93 pub max_volumes: u32,
95 pub free_volumes: u32,
97}
98
99impl From<ffi::os_fsinfo_t> for FsInfo {
100 fn from(info: ffi::os_fsinfo_t) -> Self {
101 Self {
102 max_fds: info.MaxFds,
103 free_fds: info.FreeFds,
104 max_volumes: info.MaxVolumes,
105 free_volumes: info.FreeVolumes,
106 }
107 }
108}
109
110impl FsHeader {
111 pub fn new(description: &str, subtype: u32) -> Result<Self> {
115 let mut header_uninit = MaybeUninit::<ffi::CFE_FS_Header_t>::uninit();
116
117 let mut c_desc = CString::<{ ffi::CFE_FS_HDR_DESC_MAX_LEN as usize }>::new();
119 c_desc
121 .extend_from_bytes(description.as_bytes())
122 .map_err(|_| CfsError::Osal(OsalError::FsPathTooLong))?;
123
124 unsafe {
125 ffi::CFE_FS_InitHeader(header_uninit.as_mut_ptr(), c_desc.as_ptr(), subtype);
127 Ok(Self(header_uninit.assume_init()))
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy)]
135pub enum SeekFrom {
136 Start(u32),
138 End(i32),
140 Current(i32),
142}
143
144#[derive(Debug, Clone, Copy)]
146pub struct StatVfs {
147 inner: ffi::OS_statvfs_t,
148}
149
150impl StatVfs {
151 pub fn block_size(&self) -> usize {
153 self.inner.block_size
154 }
155
156 pub fn total_blocks(&self) -> ffi::osal_blockcount_t {
158 self.inner.total_blocks
159 }
160
161 pub fn blocks_free(&self) -> ffi::osal_blockcount_t {
163 self.inner.blocks_free
164 }
165}
166
167#[derive(Debug, Clone)]
169pub struct FileProp {
170 pub path: CString<{ ffi::OS_MAX_PATH_LEN as usize }>,
172 pub user: OsalId,
174 pub is_valid: bool,
176}
177
178#[derive(Debug)]
184pub struct Directory {
185 id: ffi::osal_id_t,
186}
187
188impl Directory {
189 pub fn open(path: &str) -> Result<Self> {
191 let c_path = c_path_from_str(path)?;
192 let mut dir_id = MaybeUninit::uninit();
193 check(unsafe { ffi::OS_DirectoryOpen(dir_id.as_mut_ptr(), c_path.as_ptr()) })?;
194 Ok(Self {
195 id: unsafe { dir_id.assume_init() },
196 })
197 }
198
199 pub fn rewind(&mut self) -> Result<()> {
201 check(unsafe { ffi::OS_DirectoryRewind(self.id) })?;
202 Ok(())
203 }
204}
205
206impl Iterator for Directory {
207 type Item = Result<CString<{ ffi::OS_MAX_FILE_NAME as usize }>>;
208
209 fn next(&mut self) -> Option<Self::Item> {
214 let mut dirent = MaybeUninit::<ffi::os_dirent_t>::uninit();
215 let status = check(unsafe { ffi::OS_DirectoryRead(self.id, dirent.as_mut_ptr()) });
216
217 match status {
218 Ok(_) => {
219 let dirent = unsafe { dirent.assume_init() };
220 let c_str = unsafe { CStr::from_ptr(dirent.FileName.as_ptr()) };
221 let mut s = CString::new();
222 match s.extend_from_bytes(c_str.to_bytes()) {
223 Ok(_) => Some(Ok(s)),
224 Err(_) => Some(Err(CfsError::Osal(OsalError::NameTooLong))),
225 }
226 }
227 Err(CfsError::Osal(OsalError::Error)) => None,
228 Err(err) => Some(Err(err)),
229 }
230 }
231}
232
233impl Drop for Directory {
234 fn drop(&mut self) {
236 let _ = unsafe { ffi::OS_DirectoryClose(self.id) };
239 }
240}
241
242#[derive(Debug)]
247pub struct File {
248 id: OsalId,
249}
250
251#[derive(Debug, Clone, Copy)]
253#[repr(u32)]
254pub enum AccessMode {
255 ReadOnly = ffi::OS_READ_ONLY,
257 WriteOnly = ffi::OS_WRITE_ONLY,
259 ReadWrite = ffi::OS_READ_WRITE,
261}
262
263impl File {
264 pub fn id(&self) -> OsalId {
266 self.id
267 }
268 pub fn open(path: &str, access: AccessMode) -> Result<Self> {
275 let c_path = c_path_from_str(path)?;
276 let mut file_id = MaybeUninit::uninit();
277 let status = unsafe {
278 ffi::OS_OpenCreate(
279 file_id.as_mut_ptr(),
280 c_path.as_ptr(),
281 ffi::OS_file_flag_t_OS_FILE_FLAG_NONE as i32,
282 access as i32,
283 )
284 };
285 check(status)?;
286 Ok(Self {
287 id: OsalId(unsafe { file_id.assume_init() }),
288 })
289 }
290
291 pub fn create(path: &str) -> Result<Self> {
296 let c_path = c_path_from_str(path)?;
297 let mut file_id = MaybeUninit::uninit();
298 let flags = ffi::OS_file_flag_t_OS_FILE_FLAG_CREATE as i32
299 | ffi::OS_file_flag_t_OS_FILE_FLAG_TRUNCATE as i32;
300
301 let status = unsafe {
302 ffi::OS_OpenCreate(
303 file_id.as_mut_ptr(),
304 c_path.as_ptr(),
305 flags,
306 ffi::OS_READ_WRITE as i32,
307 )
308 };
309 check(status)?;
310 Ok(Self {
311 id: OsalId(unsafe { file_id.assume_init() }),
312 })
313 }
314
315 pub fn sync_read(&mut self, buf: &mut [u8]) -> Result<usize> {
319 let bytes_read = unsafe { ffi::OS_read(self.id.0, buf.as_mut_ptr() as *mut _, buf.len()) };
320 if bytes_read < 0 {
321 Err(CfsError::from(bytes_read))
322 } else {
323 Ok(bytes_read as usize)
324 }
325 }
326
327 pub fn sync_write(&mut self, buf: &[u8]) -> Result<usize> {
331 let bytes_written =
332 unsafe { ffi::OS_write(self.id.0, buf.as_ptr() as *const _, buf.len()) };
333 if bytes_written < 0 {
334 Err(CfsError::from(bytes_written))
335 } else {
336 Ok(bytes_written as usize)
337 }
338 }
339
340 pub fn seek(&mut self, pos: SeekFrom) -> Result<u32> {
344 let (offset, whence) = match pos {
345 SeekFrom::Start(offset) => (offset as i32, ffi::OS_SEEK_SET),
346 SeekFrom::End(offset) => (offset, ffi::OS_SEEK_END),
347 SeekFrom::Current(offset) => (offset, ffi::OS_SEEK_CUR),
348 };
349
350 let new_pos = unsafe { ffi::OS_lseek(self.id.0, offset, whence) };
351 if new_pos < 0 {
352 Err(CfsError::from(new_pos))
353 } else {
354 Ok(new_pos as u32)
355 }
356 }
357
358 pub fn read_header(&mut self) -> Result<FsHeader> {
360 let mut hdr = MaybeUninit::uninit();
361 let status = unsafe { ffi::CFE_FS_ReadHeader(hdr.as_mut_ptr(), self.id.0) };
362 check(status)?;
363 Ok(FsHeader(unsafe { hdr.assume_init() }))
364 }
365
366 pub fn write_header(&mut self, hdr: &mut FsHeader) -> Result<()> {
368 let status = unsafe { ffi::CFE_FS_WriteHeader(self.id.0, &mut hdr.0) };
369 check(status)?;
370 Ok(())
371 }
372
373 pub fn set_timestamp(&mut self, time: SysTime) -> Result<()> {
375 let status = unsafe { ffi::CFE_FS_SetTimestamp(self.id.0, time.0) };
376 check(status)?;
377 Ok(())
378 }
379
380 pub fn info(&self) -> Result<FileProp> {
382 let mut prop = MaybeUninit::<ffi::OS_file_prop_t>::uninit();
383 check(unsafe { ffi::OS_FDGetInfo(self.id.0, prop.as_mut_ptr()) })?;
384 let prop = unsafe { prop.assume_init() };
385
386 let mut path_str = CString::new();
387 let c_str = unsafe { CStr::from_ptr(prop.Path.as_ptr()) };
388 path_str
389 .extend_from_bytes(c_str.to_bytes())
390 .map_err(|_| CfsError::Osal(OsalError::FsPathTooLong))?;
391
392 Ok(FileProp {
393 path: path_str,
394 user: OsalId(prop.User),
395 is_valid: prop.IsValid != 0,
396 })
397 }
398
399 pub fn timed_read(&mut self, buf: &mut [u8], timeout_ms: i32) -> Result<usize> {
401 let bytes_read = unsafe {
402 ffi::OS_TimedRead(self.id.0, buf.as_mut_ptr() as *mut _, buf.len(), timeout_ms)
403 };
404 if bytes_read < 0 {
405 Err(CfsError::from(bytes_read))
406 } else {
407 Ok(bytes_read as usize)
408 }
409 }
410
411 #[cfg(not(nos3_cfe))]
413 pub fn timed_read_abs(&mut self, buf: &mut [u8], abstime: OsTime) -> Result<usize> {
414 let bytes_read = unsafe {
415 ffi::OS_TimedReadAbs(self.id.0, buf.as_mut_ptr() as *mut _, buf.len(), abstime.0)
416 };
417 if bytes_read < 0 {
418 Err(CfsError::from(bytes_read))
419 } else {
420 Ok(bytes_read as usize)
421 }
422 }
423
424 pub fn timed_write(&mut self, buf: &[u8], timeout_ms: i32) -> Result<usize> {
426 let bytes_written = unsafe {
427 ffi::OS_TimedWrite(self.id.0, buf.as_ptr() as *const _, buf.len(), timeout_ms)
428 };
429 if bytes_written < 0 {
430 Err(CfsError::from(bytes_written))
431 } else {
432 Ok(bytes_written as usize)
433 }
434 }
435
436 #[cfg(not(nos3_cfe))]
438 pub fn timed_write_abs(&mut self, buf: &[u8], abstime: OsTime) -> Result<usize> {
439 let bytes_written = unsafe {
440 ffi::OS_TimedWriteAbs(self.id.0, buf.as_ptr() as *const _, buf.len(), abstime.0)
441 };
442 if bytes_written < 0 {
443 Err(CfsError::from(bytes_written))
444 } else {
445 Ok(bytes_written as usize)
446 }
447 }
448}
449
450impl Drop for File {
451 fn drop(&mut self) {
453 let _ = unsafe { ffi::OS_close(self.id.0) };
454 }
455}
456
457pub fn stat(path: &str) -> Result<FileStat> {
461 let c_path = c_path_from_str(path)?;
462 let mut filestats = MaybeUninit::uninit();
463 check(unsafe { ffi::OS_stat(c_path.as_ptr(), filestats.as_mut_ptr()) })?;
464 Ok(FileStat {
465 inner: unsafe { filestats.assume_init() },
466 })
467}
468
469pub fn chmod(path: &str, mode: FileMode) -> Result<()> {
475 let c_path = c_path_from_str(path)?;
476 check(unsafe { ffi::OS_chmod(c_path.as_ptr(), mode.bits()) })?;
477 Ok(())
478}
479pub fn remove(path: &str) -> Result<()> {
484 let c_path = c_path_from_str(path)?;
485 let status = unsafe { ffi::OS_remove(c_path.as_ptr()) };
486 check(status)?;
487 Ok(())
488}
489
490pub fn rename(old: &str, new: &str) -> Result<()> {
495 let c_old = c_path_from_str(old)?;
496 let c_new = c_path_from_str(new)?;
497 let status = unsafe { ffi::OS_rename(c_old.as_ptr(), c_new.as_ptr()) };
498 check(status)?;
499 Ok(())
500}
501
502pub fn mkdir(path: &str) -> Result<()> {
504 let c_path = c_path_from_str(path)?;
505 let status = unsafe { ffi::OS_mkdir(c_path.as_ptr(), ffi::OS_READ_WRITE) };
507 check(status)?;
508 Ok(())
509}
510
511pub fn rmdir(path: &str) -> Result<()> {
513 let c_path = c_path_from_str(path)?;
514 let status = unsafe { ffi::OS_rmdir(c_path.as_ptr()) };
515 check(status)?;
516 Ok(())
517}
518
519pub fn cp(src: &str, dest: &str) -> Result<()> {
524 let c_src = c_path_from_str(src)?;
525 let c_dest = c_path_from_str(dest)?;
526 check(unsafe { ffi::OS_cp(c_src.as_ptr(), c_dest.as_ptr()) })?;
527 Ok(())
528}
529
530pub fn mv(src: &str, dest: &str) -> Result<()> {
539 let c_src = c_path_from_str(src)?;
540 let c_dest = c_path_from_str(dest)?;
541 check(unsafe { ffi::OS_mv(c_src.as_ptr(), c_dest.as_ptr()) })?;
542 Ok(())
543}
544
545pub fn statvfs(path: &str) -> Result<StatVfs> {
547 let c_path = c_path_from_str(path)?;
548 let mut statbuf = MaybeUninit::uninit();
549 check(unsafe { ffi::OS_FileSysStatVolume(c_path.as_ptr(), statbuf.as_mut_ptr()) })?;
550 Ok(StatVfs {
551 inner: unsafe { statbuf.assume_init() },
552 })
553}
554
555pub fn extract_filename_from_path<'a>(
559 original_path: &str,
560 filename_buf: &'a mut [u8; ffi::OS_MAX_PATH_LEN as usize],
561) -> Result<&'a str> {
562 let c_path = c_path_from_str(original_path)?;
563 let status = unsafe {
564 ffi::CFE_FS_ExtractFilenameFromPath(
565 c_path.as_ptr(),
566 filename_buf.as_mut_ptr() as *mut libc::c_char,
567 )
568 };
569 check(status)?;
570
571 let c_str = unsafe { CStr::from_ptr(filename_buf.as_ptr() as *const libc::c_char) };
572 c_str.to_str().map_err(|_| CfsError::InvalidString)
573}
574
575pub fn parse_input_filename<'a>(
590 output_buf: &'a mut [u8; ffi::OS_MAX_PATH_LEN as usize],
591 input_name: &str,
592 category: FileCategory,
593) -> Result<&'a str> {
594 let c_input = c_path_from_str(input_name)?;
595 let status = unsafe {
596 ffi::CFE_FS_ParseInputFileName(
597 output_buf.as_mut_ptr() as *mut libc::c_char,
598 c_input.as_ptr(),
599 output_buf.len(),
600 category as u32,
601 )
602 };
603 check(status)?;
604
605 let len = output_buf.iter().position(|&b| b == 0).unwrap_or(0);
607 core::str::from_utf8(&output_buf[..len]).map_err(|_| CfsError::InvalidString)
608}
609
610pub fn is_file_open(filename: &str) -> bool {
612 if let Ok(c_path) = c_path_from_str(filename) {
613 matches!(
614 check(unsafe { ffi::OS_FileOpenCheck(c_path.as_ptr()) }),
615 Ok(Status::Success)
616 )
617 } else {
618 false
619 }
620}
621
622pub fn close_all_files() -> Result<()> {
624 check(unsafe { ffi::OS_CloseAllFiles() })?;
625 Ok(())
626}
627
628pub fn close_file_by_name(filename: &str) -> Result<()> {
632 let c_path = c_path_from_str(filename)?;
633 check(unsafe { ffi::OS_CloseFileByName(c_path.as_ptr()) })?;
634 Ok(())
635}
636
637pub fn make_fs(
642 address: *mut u8,
643 devname: &str,
644 volname: &str,
645 blocksize: usize,
646 numblocks: usize,
647) -> Result<()> {
648 let c_dev = c_path_from_str(devname)?;
649 let c_vol = c_path_from_str(volname)?;
650 check(unsafe {
651 ffi::OS_mkfs(
652 address as *mut libc::c_char,
653 c_dev.as_ptr(),
654 c_vol.as_ptr(),
655 blocksize,
656 numblocks,
657 )
658 })?;
659 Ok(())
660}
661
662pub fn mount(devname: &str, mountpoint: &str) -> Result<()> {
664 let c_dev = c_path_from_str(devname)?;
665 let c_mount = c_path_from_str(mountpoint)?;
666 check(unsafe { ffi::OS_mount(c_dev.as_ptr(), c_mount.as_ptr()) })?;
667 Ok(())
668}
669
670pub fn unmount(mountpoint: &str) -> Result<()> {
675 let c_mount = c_path_from_str(mountpoint)?;
676 check(unsafe { ffi::OS_unmount(c_mount.as_ptr()) })?;
677 Ok(())
678}
679
680pub fn get_phys_drive_name(mountpoint: &str) -> Result<CString<{ ffi::OS_MAX_PATH_LEN as usize }>> {
682 let c_mount = c_path_from_str(mountpoint)?;
683 let mut buffer = [0u8; ffi::OS_MAX_PATH_LEN as usize];
684 check(unsafe {
685 ffi::OS_FS_GetPhysDriveName(buffer.as_mut_ptr() as *mut libc::c_char, c_mount.as_ptr())
686 })?;
687 let len = buffer.iter().position(|&b| b == 0).unwrap_or(0);
688 let mut s = CString::new();
689 s.extend_from_bytes(&buffer[..len])
690 .map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
691 Ok(s)
692}
693
694pub fn translate_path(
696 virtual_path: &str,
697) -> Result<CString<{ ffi::OS_MAX_LOCAL_PATH_LEN as usize }>> {
698 let c_virt = c_path_from_str(virtual_path)?;
699 let mut buffer = [0u8; ffi::OS_MAX_LOCAL_PATH_LEN as usize];
700 check(unsafe {
701 ffi::OS_TranslatePath(c_virt.as_ptr(), buffer.as_mut_ptr() as *mut libc::c_char)
702 })?;
703 let len = buffer.iter().position(|&b| b == 0).unwrap_or(0);
704 let mut s = CString::new();
705 s.extend_from_bytes(&buffer[..len])
706 .map_err(|_| CfsError::Osal(OsalError::NameTooLong))?;
707 Ok(s)
708}
709
710pub fn get_fs_info() -> Result<FsInfo> {
712 let mut info = MaybeUninit::uninit();
713 check(unsafe { ffi::OS_GetFsInfo(info.as_mut_ptr()) })?;
714 Ok(unsafe { info.assume_init() }.into())
715}
716
717pub fn get_default_mount_point(category: FileCategory) -> Option<&'static str> {
719 let ptr = unsafe { ffi::CFE_FS_GetDefaultMountPoint(category as u32) };
720 if ptr.is_null() {
721 None
722 } else {
723 Some(unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or(""))
724 }
725}
726
727pub fn get_default_extension(category: FileCategory) -> Option<&'static str> {
729 let ptr = unsafe { ffi::CFE_FS_GetDefaultExtension(category as u32) };
730 if ptr.is_null() {
731 None
732 } else {
733 Some(unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or(""))
734 }
735}
736
737pub fn background_file_dump_request(meta: &mut FileWriteMetaData) -> Result<()> {
739 check(unsafe { ffi::CFE_FS_BackgroundFileDumpRequest(&mut meta.0) })?;
740 Ok(())
741}
742
743pub fn is_background_file_dump_pending(meta: &FileWriteMetaData) -> bool {
745 unsafe { ffi::CFE_FS_BackgroundFileDumpIsPending(&meta.0) }
746}
747
748pub fn add_fixed_map(phys_path: &str, virt_path: &str) -> Result<OsalId> {
753 let c_phys = c_path_from_str(phys_path)?;
754 let c_virt = c_path_from_str(virt_path)?;
755 let mut filesys_id = MaybeUninit::uninit();
756 check(unsafe {
757 ffi::OS_FileSysAddFixedMap(filesys_id.as_mut_ptr(), c_phys.as_ptr(), c_virt.as_ptr())
758 })?;
759 Ok(OsalId(unsafe { filesys_id.assume_init() }))
760}
761
762pub fn init_fs(
767 address: *mut u8,
768 devname: &str,
769 volname: &str,
770 blocksize: usize,
771 numblocks: usize,
772) -> Result<()> {
773 let c_dev = c_path_from_str(devname)?;
774 let c_vol = c_path_from_str(volname)?;
775 check(unsafe {
776 ffi::OS_initfs(
777 address as *mut libc::c_char,
778 c_dev.as_ptr(),
779 c_vol.as_ptr(),
780 blocksize,
781 numblocks,
782 )
783 })?;
784 Ok(())
785}
786
787pub fn remove_fs(devname: &str) -> Result<()> {
791 let c_dev = c_path_from_str(devname)?;
792 check(unsafe { ffi::OS_rmfs(c_dev.as_ptr()) })?;
793 Ok(())
794}
795
796pub fn check_fs(path: &str, repair: bool) -> Result<()> {
800 let c_path = c_path_from_str(path)?;
801 check(unsafe { ffi::OS_chkfs(c_path.as_ptr(), repair) })?;
802 Ok(())
803}
804
805impl File {
806 pub fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> impl Future<Output = Result<usize>> + 'a {
808 core::future::poll_fn(|_| match self.timed_read(buf, 0) {
809 Ok(n) => core::task::Poll::Ready(Ok(n)),
810 Err(CfsError::Osal(OsalError::Timeout | OsalError::QueueEmpty)) => Poll::Pending,
811 Err(e) => core::task::Poll::Ready(Err(e)),
812 })
813 }
814
815 pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future<Output = Result<usize>> + 'a {
817 core::future::poll_fn(|_| match self.timed_write(buf, 0) {
818 Ok(n) => core::task::Poll::Ready(Ok(n)),
819 Err(CfsError::Osal(OsalError::Timeout | OsalError::QueueFull)) => Poll::Pending,
820 Err(e) => core::task::Poll::Ready(Err(e)),
821 })
822 }
823}