diff --git a/cpukit/include/rtems/confdefs/libio.h b/cpukit/include/rtems/confdefs/libio.h index d59da704d1..c14941dabd 100644 --- a/cpukit/include/rtems/confdefs/libio.h +++ b/cpukit/include/rtems/confdefs/libio.h @@ -48,6 +48,7 @@ #ifdef CONFIGURE_FILESYSTEM_ALL #define CONFIGURE_FILESYSTEM_DOSFS + #define CONFIGURE_FILESYSTEM_FATFS #define CONFIGURE_FILESYSTEM_FTPFS #define CONFIGURE_FILESYSTEM_IMFS #define CONFIGURE_FILESYSTEM_JFFS2 @@ -108,6 +109,10 @@ #error "CONFIGURE_APPLICATION_DISABLE_FILESYSTEM cannot be used together with CONFIGURE_FILESYSTEM_DOSFS" #endif + #ifdef CONFIGURE_FILESYSTEM_FATFS + #error "CONFIGURE_APPLICATION_DISABLE_FILESYSTEM cannot be used together with CONFIGURE_FILESYSTEM_FATFS" + #endif + #ifdef CONFIGURE_FILESYSTEM_FTPFS #error "CONFIGURE_APPLICATION_DISABLE_FILESYSTEM cannot be used together with CONFIGURE_FILESYSTEM_FTPFS" #endif @@ -139,6 +144,10 @@ #include #endif +#ifdef CONFIGURE_FILESYSTEM_FATFS +#include +#endif + #ifdef CONFIGURE_FILESYSTEM_FTPFS #include #endif @@ -297,6 +306,9 @@ const rtems_filesystem_table_t rtems_filesystem_table[] = { #ifdef CONFIGURE_FILESYSTEM_DOSFS { RTEMS_FILESYSTEM_TYPE_DOSFS, rtems_dosfs_initialize }, #endif + #ifdef CONFIGURE_FILESYSTEM_FATFS + { RTEMS_FILESYSTEM_TYPE_FATFS, rtems_fatfs_initialize }, + #endif #ifdef CONFIGURE_FILESYSTEM_FTPFS { RTEMS_FILESYSTEM_TYPE_FTPFS, rtems_ftpfs_initialize }, #endif diff --git a/cpukit/include/rtems/fatfs.h b/cpukit/include/rtems/fatfs.h new file mode 100644 index 0000000000..93f8d7c4f1 --- /dev/null +++ b/cpukit/include/rtems/fatfs.h @@ -0,0 +1,60 @@ +/** + * @file + * + * @ingroup FatFS + * + * @brief RTEMS FatFS Filesystem Public API + */ + +/* + Copyright (C) 2025 Sepehr Ganji +*/ + +#ifndef _RTEMS_FATFS_H +#define _RTEMS_FATFS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup FATFS + * + * @{ + */ + +/** + * @brief RTEMS FatFS filesystem mount options. + */ +typedef struct { + /** Currently no options defined */ + int reserved; +} rtems_fatfs_mount_options; + +/** + * @brief Initialize FatFS filesystem. + * + * This function is the mount handler for the FatFS filesystem. It is called + * by the mount() system call when the filesystem type is + * RTEMS_FILESYSTEM_TYPE_FATFS. + * + * @param[in] mt_entry The mount table entry. + * @param[in] data Mount options (rtems_fatfs_mount_options or NULL). + * + * @return 0 on success, -1 on error with errno set. + */ +int rtems_fatfs_initialize( + rtems_filesystem_mount_table_entry_t *mt_entry, + const void *data +); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _RTEMS_FATFS_H */ diff --git a/cpukit/include/rtems/libio.h b/cpukit/include/rtems/libio.h index e2e8aefcef..853bb51aa1 100644 --- a/cpukit/include/rtems/libio.h +++ b/cpukit/include/rtems/libio.h @@ -1649,6 +1649,7 @@ extern int rtems_mkdir(const char *path, mode_t mode); #define RTEMS_FILESYSTEM_TYPE_TFTPFS "tftpfs" #define RTEMS_FILESYSTEM_TYPE_NFS "nfs" #define RTEMS_FILESYSTEM_TYPE_DOSFS "dosfs" +#define RTEMS_FILESYSTEM_TYPE_FATFS "fatfs" #define RTEMS_FILESYSTEM_TYPE_RFS "rfs" #define RTEMS_FILESYSTEM_TYPE_JFFS2 "jffs2" diff --git a/cpukit/libfs/src/fatfs/rtems-diskio.c b/cpukit/libfs/src/fatfs/rtems-diskio.c new file mode 100644 index 0000000000..bc7b0395f4 --- /dev/null +++ b/cpukit/libfs/src/fatfs/rtems-diskio.c @@ -0,0 +1,222 @@ +/** + * @file + * + * @ingroup FatFS + * + * @brief RTEMS Disk I/O Interface for FatFs + */ + +/* + Copyright (C) 2025 Sepehr Ganji +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ff.h" +#include "diskio.h" +#include "rtems-fatfs.h" + +static fatfs_volume_t volumes[ FF_VOLUMES ]; + +/* + * Compiler will change name to rtems_fatfs_disk_status + */ +DSTATUS disk_status( BYTE pdrv ) +{ + if ( pdrv >= FF_VOLUMES ) { + return STA_NOINIT; + } + + if ( !volumes[ pdrv ].initialized ) { + return STA_NOINIT; + } + + return 0; +} + +/* + * Compiler will change name to rtems_fatfs_disk_initialize + */ +DSTATUS disk_initialize( BYTE pdrv ) +{ + if ( pdrv >= FF_VOLUMES ) { + return STA_NOINIT; + } + bool is_initialized = volumes[ pdrv ].initialized; + return is_initialized ? 0 : STA_NOINIT; +} + +/* + * Compiler will change name to rtems_fatfs_disk_read + */ +DRESULT disk_read( BYTE pdrv, BYTE *buff, LBA_t sector, UINT count ) +{ + rtems_status_code sc; + rtems_bdbuf_buffer *bd; + uint32_t block_size; + + if ( disk_status( pdrv ) != 0 ) { + return RES_NOTRDY; + } + + rtems_disk_device *dd = volumes[ pdrv ].dd; + block_size = dd->block_size; + + for ( size_t i = 0; i < count; i++ ) { + sc = rtems_bdbuf_read( dd, sector + i, &bd ); + if ( sc != RTEMS_SUCCESSFUL ) { + return RES_ERROR; + } + + memcpy( buff + ( i * block_size ), bd->buffer, block_size ); + + sc = rtems_bdbuf_release( bd ); + if ( sc != RTEMS_SUCCESSFUL ) { + return RES_ERROR; + } + } + + return RES_OK; +} + +/* + * Compiler will change name to rtems_fatfs_disk_write + */ +DRESULT disk_write( BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count ) +{ + rtems_status_code sc; + rtems_bdbuf_buffer *bd; + uint32_t block_size; + + if ( disk_status( pdrv ) != 0 ) { + return RES_NOTRDY; + } + + rtems_disk_device *dd = volumes[ pdrv ].dd; + + block_size = dd->block_size; + + for ( size_t i = 0; i < count; i++ ) { + sc = rtems_bdbuf_get( dd, sector + i, &bd ); + if ( sc != RTEMS_SUCCESSFUL ) { + return RES_ERROR; + } + + memcpy( bd->buffer, buff + ( i * block_size ), block_size ); + + sc = rtems_bdbuf_release_modified( bd ); + if ( sc != RTEMS_SUCCESSFUL ) { + return RES_ERROR; + } + } + + return RES_OK; +} + +/* + * Compiler will change name to rtems_fatfs_disk_ioctl + */ +DRESULT disk_ioctl( BYTE pdrv, BYTE cmd, void *buff ) +{ + if ( disk_status( pdrv ) != 0 ) { + return RES_NOTRDY; + } + + rtems_disk_device *dd = volumes[ pdrv ].dd; + + switch ( cmd ) { + case CTRL_SYNC: { + rtems_status_code sc = rtems_bdbuf_syncdev( dd ); + if ( sc != RTEMS_SUCCESSFUL ) { + return RES_ERROR; + } + } break; + + case GET_SECTOR_COUNT: + *(rtems_blkdev_bnum *) buff = dd->size; + break; + + case GET_SECTOR_SIZE: + *(uint32_t *) buff = dd->media_block_size; + break; + + case GET_BLOCK_SIZE: + *(uint32_t *) buff = dd->block_size; + break; + + case CTRL_TRIM: + break; + + default: + return RES_PARERR; + } + + return RES_OK; +} + +int fatfs_diskio_register_device( uint8_t pdrv, const char *device_path ) +{ + struct stat stat_buf; + int fd; + rtems_disk_device *dd; + int rc; + + if ( pdrv >= FF_VOLUMES ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + if ( volumes[ pdrv ].initialized ) { + return 0; + } + + fd = open( device_path, O_RDWR ); + if ( fd < 0 ) { + return -1; + } + + rc = fstat( fd, &stat_buf ); + if ( rc != 0 ) { + close( fd ); + return -1; + } + + if ( !S_ISBLK( stat_buf.st_mode ) ) { + close( fd ); + rtems_set_errno_and_return_minus_one( ENXIO ); + } + + rc = rtems_disk_fd_get_disk_device( fd, &dd ); + if ( rc != 0 ) { + close( fd ); + return -1; + } + + volumes[ pdrv ].fd = fd; + volumes[ pdrv ].dd = dd; + volumes[ pdrv ].initialized = true; + + return 0; +} + +void fatfs_diskio_unregister_device( uint8_t pdrv ) +{ + if ( pdrv >= FF_VOLUMES || !volumes[ pdrv ].initialized ) { + return; + } + + close( volumes[ pdrv ].fd ); + volumes[ pdrv ].fd = -1; + volumes[ pdrv ].dd = NULL; + volumes[ pdrv ].initialized = false; +} diff --git a/cpukit/libfs/src/fatfs/rtems-fatfs-dir.c b/cpukit/libfs/src/fatfs/rtems-fatfs-dir.c new file mode 100644 index 0000000000..9a4435b56e --- /dev/null +++ b/cpukit/libfs/src/fatfs/rtems-fatfs-dir.c @@ -0,0 +1,207 @@ +/** + * @file + * + * @ingroup FatFS + * + * @brief RTEMS FATFS directory operations + */ + +/* + Copyright (C) 2025 Sepehr Ganji +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ff.h" +#include "rtems-fatfs.h" + +#define DIR POSIX_DIR +#include +#undef DIR + +ssize_t rtems_fatfs_dir_read( rtems_libio_t *iop, void *buffer, size_t count ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + struct dirent *dp = (struct dirent *) buffer; + FILINFO fno; + FRESULT fr; + size_t bytes_transferred = 0; + + if ( node == NULL || !node->is_directory ) { + rtems_set_errno_and_return_minus_one( ENOTDIR ); + } + + if ( count < sizeof( struct dirent ) ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + memset( &fno, 0, sizeof( fno ) ); + + fr = f_readdir( &node->handle.dir, &fno ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + if ( fno.fname[ 0 ] == '\0' ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + return 0; + } + + memset( dp, 0, sizeof( struct dirent ) ); + dp->d_ino = 1; + dp->d_off = iop->offset; + dp->d_reclen = sizeof( struct dirent ); + + size_t name_len = strlen( fno.fname ); + if ( name_len >= sizeof( dp->d_name ) ) { + name_len = sizeof( dp->d_name ) - 1; + } + memcpy( dp->d_name, fno.fname, name_len ); + dp->d_name[ name_len ] = '\0'; + dp->d_namlen = name_len; + + if ( fno.fattrib & AM_DIR ) { + dp->d_type = DT_DIR; + } else { + dp->d_type = DT_REG; + } + + bytes_transferred = sizeof( struct dirent ); + iop->offset += sizeof( struct dirent ); + + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + return (ssize_t) bytes_transferred; +} + +int rtems_fatfs_dir_fstat( + const rtems_filesystem_location_info_t *loc, + struct stat *buf +) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) loc->node_access; + + if ( node == NULL ) { + rtems_set_errno_and_return_minus_one( EBADF ); + } + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) + loc->mt_entry->fs_info; + rtems_fatfs_node_to_stat( node, buf, fs_info ); + return 0; +} + +int rtems_fatfs_opendir( + rtems_libio_t *iop, + const char *path, + int oflag, + mode_t mode +) +{ + (void) path; + (void) oflag; + (void) mode; + + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) + iop->pathinfo.mt_entry->fs_info; + char full_path[ 256 ]; + FRESULT fr; + int rc; + + if ( node == NULL || !node->is_directory ) { + rtems_set_errno_and_return_minus_one( ENOTDIR ); + } + + if ( node->is_open ) { + return 0; + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + rc = rtems_fatfs_resolve_node_path( + fs_info, + node, + full_path, + sizeof( full_path ), + iop->pathinfo.mt_entry + ); + if ( rc != 0 ) { + return rc; + } + + fr = f_opendir( &node->handle.dir, full_path ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + node->is_open = true; + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + return 0; +} + +int rtems_fatfs_closedir( rtems_libio_t *iop ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FRESULT fr; + + if ( node == NULL || !node->is_directory ) { + rtems_set_errno_and_return_minus_one( ENOTDIR ); + } + + if ( !node->is_open ) { + return 0; + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + fr = f_closedir( &node->handle.dir ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + node->is_open = false; + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + return 0; +} + +const rtems_filesystem_file_handlers_r rtems_fatfs_dir_handlers = { + .open_h = rtems_fatfs_opendir, + .close_h = rtems_fatfs_closedir, + .read_h = rtems_fatfs_dir_read, + .write_h = rtems_filesystem_default_write, + .ioctl_h = rtems_filesystem_default_ioctl, + .lseek_h = rtems_filesystem_default_lseek_directory, + .fstat_h = rtems_fatfs_dir_fstat, + .ftruncate_h = rtems_filesystem_default_ftruncate_directory, + .fsync_h = rtems_fatfs_file_fsync, + .fdatasync_h = rtems_fatfs_file_fsync, + .fcntl_h = rtems_filesystem_default_fcntl, + .kqfilter_h = rtems_filesystem_default_kqfilter, + .mmap_h = rtems_filesystem_default_mmap, + .poll_h = rtems_filesystem_default_poll, + .readv_h = rtems_filesystem_default_readv, + .writev_h = rtems_filesystem_default_writev +}; diff --git a/cpukit/libfs/src/fatfs/rtems-fatfs-file.c b/cpukit/libfs/src/fatfs/rtems-fatfs-file.c new file mode 100644 index 0000000000..00ff49a5d1 --- /dev/null +++ b/cpukit/libfs/src/fatfs/rtems-fatfs-file.c @@ -0,0 +1,451 @@ +/** + * @file + * + * @ingroup FatFS + * + * @brief RTEMS FATFS file operations + */ + +/* + Copyright (C) 2025 Sepehr Ganji +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ff.h" +#include "rtems-fatfs.h" + +ssize_t rtems_fatfs_file_read( rtems_libio_t *iop, void *buffer, size_t count ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FRESULT fr; + UINT bytes_read = 0; + + if ( node == NULL || node->is_directory ) { + rtems_set_errno_and_return_minus_one( EBADF ); + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + fr = f_lseek( &node->handle.file, (FSIZE_t) iop->offset ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + fr = f_read( &node->handle.file, buffer, (UINT) count, &bytes_read ); + + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + if ( bytes_read > 0 ) { + iop->offset += bytes_read; + } + + return (ssize_t) bytes_read; +} + +ssize_t rtems_fatfs_file_write( + rtems_libio_t *iop, + const void *buffer, + size_t count +) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FRESULT fr; + UINT bytes_written = 0; + + if ( node == NULL || node->is_directory ) { + rtems_set_errno_and_return_minus_one( EBADF ); + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + if ( rtems_libio_iop_is_append( iop ) ) { + fr = f_lseek( &node->handle.file, f_size( &node->handle.file ) ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + iop->offset = f_tell( &node->handle.file ); + } else { + FSIZE_t current_size = f_size( &node->handle.file ); + FSIZE_t target_pos = (FSIZE_t) iop->offset; + + if ( target_pos > current_size ) { + FSIZE_t gap_size = target_pos - current_size; + char zero_buf[ 512 ]; + UINT bw; + + memset( zero_buf, 0, sizeof( zero_buf ) ); + + fr = f_lseek( &node->handle.file, current_size ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + while ( gap_size > 0 && fr == FR_OK ) { + UINT to_write = gap_size > sizeof( zero_buf ) ? sizeof( zero_buf ) : + (UINT) gap_size; + fr = f_write( &node->handle.file, zero_buf, to_write, &bw ); + if ( fr == FR_OK && bw == to_write ) { + gap_size -= to_write; + } else { + break; + } + } + + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + if ( gap_size > 0 ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = ENOSPC; + return -1; + } + } else { + fr = f_lseek( &node->handle.file, target_pos ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + } + } + + fr = f_write( &node->handle.file, buffer, (UINT) count, &bytes_written ); + + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + if ( bytes_written > 0 ) { + iop->offset += bytes_written; + node->info.fsize = f_size( &node->handle.file ); + + time_t current_time = time( NULL ); + node->posix_mtime = current_time; + node->posix_ctime = current_time; + node->posix_time_valid = true; + } else if ( count > 0 && bytes_written == 0 ) { + errno = ENOSPC; + return -1; + } + + return (ssize_t) bytes_written; +} + +off_t rtems_fatfs_file_lseek( rtems_libio_t *iop, off_t offset, int whence ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FSIZE_t new_pos; + off_t result; + + if ( node == NULL || node->is_directory ) { + rtems_set_errno_and_return_minus_one( EBADF ); + } + + switch ( whence ) { + case SEEK_SET: + if ( offset < 0 ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + new_pos = (FSIZE_t) offset; + break; + case SEEK_CUR: + if ( offset > 0 && iop->offset > LLONG_MAX - offset ) { + rtems_set_errno_and_return_minus_one( EOVERFLOW ); + } + result = iop->offset + offset; + if ( result < 0 ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + new_pos = (FSIZE_t) result; + break; + case SEEK_END: + if ( offset > 0 && (off_t) node->info.fsize > LLONG_MAX - offset ) { + rtems_set_errno_and_return_minus_one( EOVERFLOW ); + } + result = (off_t) node->info.fsize + offset; + if ( result < 0 ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + new_pos = (FSIZE_t) result; + break; + default: + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + iop->offset = (off_t) new_pos; + return iop->offset; +} + +int rtems_fatfs_file_fstat( + const rtems_filesystem_location_info_t *loc, + struct stat *buf +) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) loc->node_access; + + if ( node == NULL ) { + rtems_set_errno_and_return_minus_one( EBADF ); + } + + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) + loc->mt_entry->fs_info; + rtems_fatfs_node_to_stat( node, buf, fs_info ); + return 0; +} + +int rtems_fatfs_file_ftruncate( rtems_libio_t *iop, off_t length ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FRESULT fr; + FSIZE_t old_size; + + if ( node == NULL || node->is_directory ) { + rtems_set_errno_and_return_minus_one( EBADF ); + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + old_size = f_size( &node->handle.file ); + + if ( (FSIZE_t) length > old_size ) { + FSIZE_t current_pos = f_tell( &node->handle.file ); + FSIZE_t gap_size = (FSIZE_t) length - old_size; + char zero_buf[ 512 ]; + UINT bw; + + memset( zero_buf, 0, sizeof( zero_buf ) ); + + fr = f_lseek( &node->handle.file, old_size ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + while ( gap_size > 0 && fr == FR_OK ) { + UINT to_write = gap_size > sizeof( zero_buf ) ? sizeof( zero_buf ) : + (UINT) gap_size; + fr = f_write( &node->handle.file, zero_buf, to_write, &bw ); + if ( fr == FR_OK && bw == to_write ) { + gap_size -= to_write; + } else { + break; + } + } + + if ( fr == FR_OK ) { + if ( gap_size > 0 ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = ENOSPC; + return -1; + } + fr = f_lseek( &node->handle.file, current_pos ); + } + } else { + fr = f_lseek( &node->handle.file, (FSIZE_t) length ); + if ( fr == FR_OK ) { + fr = f_truncate( &node->handle.file ); + } + } + + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + node->info.fsize = (FSIZE_t) length; + + return 0; +} + +int rtems_fatfs_file_fsync( rtems_libio_t *iop ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FRESULT fr; + + if ( node == NULL || node->is_directory || !node->is_open ) { + return 0; + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + fr = f_sync( &node->handle.file ); + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + return 0; +} + +int rtems_fatfs_openfile( + rtems_libio_t *iop, + const char *path, + int oflag, + mode_t mode +) +{ + (void) path; + (void) mode; + + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) + iop->pathinfo.mt_entry->fs_info; + char full_path[ 256 ]; + FRESULT fr; + int rc; + + if ( node == NULL || node->is_directory ) { + rtems_set_errno_and_return_minus_one( EISDIR ); + } + + if ( node->is_open ) { + return 0; + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + rc = rtems_fatfs_resolve_node_path( + fs_info, + node, + full_path, + sizeof( full_path ), + iop->pathinfo.mt_entry + ); + if ( rc != 0 ) { + return rc; + } + + BYTE fatfs_mode = FA_READ; + + if ( oflag & O_WRONLY ) { + fatfs_mode = FA_WRITE; + } else if ( oflag & O_RDWR ) { + fatfs_mode = FA_READ | FA_WRITE; + } + + if ( oflag & O_CREAT ) { + if ( oflag & O_WRONLY ) { + fatfs_mode = FA_WRITE | FA_OPEN_EXISTING; + } else if ( oflag & O_RDWR ) { + fatfs_mode = FA_READ | FA_WRITE | FA_OPEN_EXISTING; + } else { + fatfs_mode = FA_READ | FA_OPEN_EXISTING; + } + } + + fr = f_open( &node->handle.file, full_path, fatfs_mode ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + if ( oflag & O_TRUNC ) { + if ( f_size( &node->handle.file ) == 0 ) { + UINT bw; + char dummy = 0; + fr = f_write( &node->handle.file, &dummy, 1, &bw ); + if ( fr == FR_OK ) { + fr = f_truncate( &node->handle.file ); + } + } else { + fr = f_truncate( &node->handle.file ); + } + + if ( fr != FR_OK ) { + f_close( &node->handle.file ); + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + f_sync( &node->handle.file ); + } + + node->is_open = true; + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + return 0; +} + +int rtems_fatfs_closefile( rtems_libio_t *iop ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) iop->pathinfo.node_access; + FRESULT fr; + + if ( node == NULL || node->is_directory ) { + rtems_set_errno_and_return_minus_one( EISDIR ); + } + + if ( !node->is_open ) { + return 0; + } + + rtems_fatfs_lock( iop->pathinfo.mt_entry ); + + fr = f_close( &node->handle.file ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + node->is_open = false; + rtems_fatfs_unlock( iop->pathinfo.mt_entry ); + + return 0; +} + +const rtems_filesystem_file_handlers_r rtems_fatfs_file_handlers = { + .open_h = rtems_fatfs_openfile, + .close_h = rtems_fatfs_closefile, + .read_h = rtems_fatfs_file_read, + .write_h = rtems_fatfs_file_write, + .ioctl_h = rtems_filesystem_default_ioctl, + .lseek_h = rtems_fatfs_file_lseek, + .fstat_h = rtems_fatfs_file_fstat, + .ftruncate_h = rtems_fatfs_file_ftruncate, + .fsync_h = rtems_fatfs_file_fsync, + .fdatasync_h = rtems_fatfs_file_fsync, + .fcntl_h = rtems_filesystem_default_fcntl, + .kqfilter_h = rtems_filesystem_default_kqfilter, + .mmap_h = rtems_filesystem_default_mmap, + .poll_h = rtems_filesystem_default_poll, + .readv_h = rtems_filesystem_default_readv, + .writev_h = rtems_filesystem_default_writev +}; diff --git a/cpukit/libfs/src/fatfs/rtems-fatfs-init.c b/cpukit/libfs/src/fatfs/rtems-fatfs-init.c new file mode 100644 index 0000000000..a983fd40cd --- /dev/null +++ b/cpukit/libfs/src/fatfs/rtems-fatfs-init.c @@ -0,0 +1,837 @@ +/** + * @file + * + * @ingroup FatFS + * + * @brief RTEMS FATFS initialization + */ + +/* + Copyright (C) 2025 Sepehr Ganji +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ff.h" +#include "rtems-fatfs.h" + +static uint8_t next_drive_number = 0; + +static int rtems_fatfs_build_full_path( + const rtems_fatfs_fs_info_t *fs_info, + const rtems_fatfs_node_t *parent_node, + const char *name, + size_t namelen, + char *path, + size_t path_size +) +{ + size_t mount_len = strlen( fs_info->mount_path ); + size_t total_len; + char *write_pos = path; + + if ( parent_node == NULL ) { + total_len = mount_len + namelen; + if ( total_len >= path_size ) { + rtems_set_errno_and_return_minus_one( ENAMETOOLONG ); + } + memcpy( write_pos, fs_info->mount_path, mount_len ); + write_pos += mount_len; + memcpy( write_pos, name, namelen ); + write_pos += namelen; + *write_pos = '\0'; + } else { + size_t parent_len = strlen( parent_node->path ); + bool need_slash = ( strcmp( parent_node->path, "/" ) != 0 ); + total_len = mount_len + parent_len + ( need_slash ? 1 : 0 ) + namelen; + + if ( total_len >= path_size ) { + rtems_set_errno_and_return_minus_one( ENAMETOOLONG ); + } + + memcpy( write_pos, fs_info->mount_path, mount_len ); + write_pos += mount_len; + memcpy( write_pos, parent_node->path, parent_len ); + write_pos += parent_len; + if ( need_slash ) { + *write_pos = '/'; + write_pos++; + } + memcpy( write_pos, name, namelen ); + write_pos += namelen; + *write_pos = '\0'; + } + + char *dotdot; + while ( ( dotdot = strstr( path, "/.." ) ) != NULL ) { + if ( dotdot[ 3 ] == '\0' || dotdot[ 3 ] == '/' ) { + char *start = dotdot - 1; + while ( start > path && *start != '/' ) { + start--; + } + if ( start >= path && *start == '/' ) { + char *end = dotdot + 3; + memmove( start, end, strlen( end ) + 1 ); + if ( start == path && *start == '\0' ) { + strcpy( path, "/" ); + } + } else { + break; + } + } else { + break; + } + } + + size_t len = strlen( path ); + if ( len > 0 && len <= 2 && path[ len - 1 ] == ':' ) { + strcat( path, "/" ); + } + + return 0; +} + +static void rtems_fatfs_cache_posix_times( + rtems_fatfs_fs_info_t *fs_info, + const char *path, + time_t mtime, + time_t ctime +) +{ + int index = fs_info->posix_cache_next; + size_t path_len = strlen( path ); + if ( path_len >= sizeof( fs_info->posix_cache[ index ].path ) ) { + path_len = sizeof( fs_info->posix_cache[ index ].path ) - 1; + } + memcpy( fs_info->posix_cache[ index ].path, path, path_len ); + fs_info->posix_cache[ index ].path[ path_len ] = '\0'; + fs_info->posix_cache[ index ].mtime = mtime; + fs_info->posix_cache[ index ].ctime = ctime; + fs_info->posix_cache[ index ].valid = true; + fs_info->posix_cache_next = ( fs_info->posix_cache_next + 1 ) % + RTEMS_FATFS_POSIX_CACHE_SIZE; +} + +static void rtems_fatfs_set_parent_timestamps( + const rtems_fatfs_node_t *parent_node +) +{ + if ( parent_node != NULL && parent_node->is_directory ) { + rtems_fatfs_node_t *mutable_parent = (rtems_fatfs_node_t *) parent_node; + time_t current_time = time( NULL ); + + mutable_parent->posix_mtime = current_time; + mutable_parent->posix_ctime = current_time; + mutable_parent->posix_time_valid = true; + } +} + +int rtems_fatfs_initialize( + rtems_filesystem_mount_table_entry_t *mt_entry, + const void *data +) +{ + (void) data; + + rtems_fatfs_fs_info_t *fs_info = NULL; + rtems_fatfs_node_t *root_node = NULL; + FRESULT fr; + char drive_path[ 8 ]; + int rc = -1; + + fs_info = calloc( 1, sizeof( *fs_info ) ); + if ( fs_info == NULL ) { + rtems_set_errno_and_return_minus_one( ENOMEM ); + } + + root_node = calloc( 1, sizeof( *root_node ) ); + if ( root_node == NULL ) { + free( fs_info ); + rtems_set_errno_and_return_minus_one( ENOMEM ); + } + + rtems_recursive_mutex_init( + &fs_info->vol_mutex, + RTEMS_FILESYSTEM_TYPE_FATFS + ); + + fs_info->drive_number = next_drive_number++; + if ( next_drive_number >= FF_VOLUMES ) { + next_drive_number = 0; + } + + rc = fatfs_diskio_register_device( fs_info->drive_number, mt_entry->dev ); + if ( rc != 0 ) { + rtems_recursive_mutex_destroy( &fs_info->vol_mutex ); + free( root_node ); + free( fs_info ); + return -1; + } + + drive_path[ 0 ] = '0' + fs_info->drive_number; + drive_path[ 1 ] = ':'; + drive_path[ 2 ] = '\0'; + fr = f_mount( &fs_info->fatfs, drive_path, 1 ); + if ( fr != FR_OK ) { + fatfs_diskio_unregister_device( fs_info->drive_number ); + rtems_recursive_mutex_destroy( &fs_info->vol_mutex ); + free( root_node ); + free( fs_info ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + fs_info->file_handlers = &rtems_fatfs_file_handlers; + fs_info->dir_handlers = &rtems_fatfs_dir_handlers; + + strncpy( fs_info->mount_path, drive_path, sizeof( fs_info->mount_path ) - 1 ); + fs_info->mount_path[ sizeof( fs_info->mount_path ) - 1 ] = '\0'; + + strcpy( fs_info->current_dir, "/" ); + + memset( fs_info->posix_cache, 0, sizeof( fs_info->posix_cache ) ); + fs_info->posix_cache_next = 0; + + root_node->is_directory = true; + root_node->is_open = false; + root_node->is_root_node = true; + root_node->ref_count = 1; + strcpy( root_node->path, "/" ); + + memset( &root_node->info, 0, sizeof( root_node->info ) ); + root_node->info.fattrib = AM_DIR; + root_node->info.fsize = 0; + root_node->info.fdate = 0; + root_node->info.ftime = 0; + strcpy( root_node->info.fname, "/" ); + + mt_entry->fs_info = fs_info; + mt_entry->ops = &rtems_fatfs_ops; + mt_entry->mt_fs_root->location.node_access = root_node; + mt_entry->mt_fs_root->location.handlers = fs_info->dir_handlers; + + return 0; +} + +void rtems_fatfs_fsunmount_me( rtems_filesystem_mount_table_entry_t *mt_entry ) +{ + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) mt_entry->fs_info; + rtems_fatfs_node_t *root_node = NULL; + + if ( fs_info != NULL ) { + if ( mt_entry->mt_fs_root != NULL ) { + root_node = (rtems_fatfs_node_t *) + mt_entry->mt_fs_root->location.node_access; + } + + f_mount( NULL, fs_info->mount_path, 1 ); + + fatfs_diskio_unregister_device( fs_info->drive_number ); + + if ( root_node != NULL ) { + free( root_node ); + } + + rtems_recursive_mutex_destroy( &fs_info->vol_mutex ); + + free( fs_info ); + } +} + +static rtems_fatfs_node_t *rtems_fatfs_node_get( rtems_fatfs_node_t *node ) +{ + if ( node != NULL ) { + node->ref_count++; + } + return node; +} + +static void rtems_fatfs_node_put( rtems_fatfs_node_t *node ) +{ + if ( node != NULL ) { + if ( node->is_root_node ) { + node->ref_count--; + return; + } + + node->ref_count--; + if ( node->ref_count <= 0 ) { + if ( node->is_open ) { + if ( node->is_directory ) { + f_closedir( &node->handle.dir ); + } else { + f_close( &node->handle.file ); + } + } + + free( node ); + } + } +} + +static int rtems_fatfs_find_name( + rtems_filesystem_location_info_t *parentloc, + const char *name, + size_t name_len +) +{ + rtems_fatfs_fs_info_t *fs_info = parentloc->mt_entry->fs_info; + rtems_fatfs_node_t *parent_node = parentloc->node_access; + rtems_fatfs_node_t *new_node = NULL; + FRESULT fr; + FILINFO fno; + char fatfs_path[ 256 ]; + int ret; + + ret = rtems_fatfs_build_full_path( + fs_info, + parent_node, + name, + name_len, + fatfs_path, + sizeof( fatfs_path ) + ); + if ( ret != 0 ) { + return ret; + } + + if ( strlen( fatfs_path ) == 3 && fatfs_path[ 1 ] == ':' && + fatfs_path[ 2 ] == '/' ) { + rtems_filesystem_mount_table_entry_t *mt_entry = parentloc->mt_entry; + if ( mt_entry && mt_entry->mt_fs_root ) { + new_node = (rtems_fatfs_node_t *) + mt_entry->mt_fs_root->location.node_access; + if ( new_node ) { + rtems_fatfs_node_get( new_node ); + rtems_fatfs_node_put( parent_node ); + parentloc->node_access = new_node; + return 0; + } + } + rtems_set_errno_and_return_minus_one( ENOENT ); + } + + fr = f_stat( fatfs_path, &fno ); + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + new_node = malloc( sizeof( *new_node ) ); + if ( new_node == NULL ) { + rtems_set_errno_and_return_minus_one( ENOMEM ); + } + memset( new_node, 0, sizeof( *new_node ) ); + + new_node->info = fno; + new_node->is_directory = ( fno.fattrib & AM_DIR ) != 0; + new_node->is_open = false; + new_node->is_root_node = false; + new_node->ref_count = 1; + + size_t total_len; + if ( strcmp( parent_node->path, "/" ) == 0 ) { + total_len = 1 + name_len; // "/" + name + if ( total_len >= sizeof( new_node->path ) ) { + free( new_node ); + rtems_set_errno_and_return_minus_one( ENAMETOOLONG ); + } + new_node->path[ 0 ] = '/'; + memcpy( new_node->path + 1, name, name_len ); + new_node->path[ total_len ] = '\0'; + } else { + size_t parent_len = strlen( parent_node->path ); + total_len = parent_len + 1 + name_len; // parent + "/" + name + if ( total_len >= sizeof( new_node->path ) ) { + free( new_node ); + rtems_set_errno_and_return_minus_one( ENAMETOOLONG ); + } + memcpy( new_node->path, parent_node->path, parent_len ); + new_node->path[ parent_len ] = '/'; + memcpy( new_node->path + parent_len + 1, name, name_len ); + new_node->path[ total_len ] = '\0'; + } + + rtems_fatfs_node_put( parent_node ); + + parentloc->node_access = new_node; + return 0; +} + +static void rtems_fatfs_set_handlers( rtems_filesystem_location_info_t *loc ) +{ + rtems_fatfs_fs_info_t *fs_info = loc->mt_entry->fs_info; + rtems_fatfs_node_t *node = loc->node_access; + + if ( node->is_directory ) { + loc->handlers = fs_info->dir_handlers; + } else { + loc->handlers = fs_info->file_handlers; + } +} + +static bool rtems_fatfs_eval_is_directory( + rtems_filesystem_eval_path_context_t *ctx, + void *arg +) +{ + (void) arg; + + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) ctx->currentloc.node_access; + return node != NULL && node->is_directory; +} + +static rtems_filesystem_eval_path_generic_status rtems_fatfs_eval_token( + rtems_filesystem_eval_path_context_t *ctx, + void *arg, + const char *token, + size_t tokenlen +) +{ + (void) arg; + + rtems_filesystem_eval_path_generic_status + status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_DONE; + + if ( rtems_filesystem_is_current_directory( token, tokenlen ) ) { + rtems_filesystem_eval_path_clear_token( ctx ); + status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_CONTINUE; + } else { + rtems_filesystem_location_info_t + *currentloc = rtems_filesystem_eval_path_get_currentloc( ctx ); + + int rc = rtems_fatfs_find_name( currentloc, token, tokenlen ); + + if ( rc == 0 ) { + rtems_filesystem_eval_path_clear_token( ctx ); + rtems_fatfs_set_handlers( currentloc ); + + if ( rtems_filesystem_eval_path_has_path( ctx ) ) { + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) + currentloc->node_access; + if ( node != NULL && !node->is_directory ) { + rtems_filesystem_eval_path_error( ctx, ENOTDIR ); + status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_DONE; + } else { + status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_CONTINUE; + } + } else { + status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_DONE; + } + } else { + if ( errno == ENOENT ) { + status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_NO_ENTRY; + } else { + rtems_filesystem_eval_path_error( ctx, errno ); + } + } + } + + return status; +} + +static const rtems_filesystem_eval_path_generic_config rtems_fatfs_eval_config = + { .is_directory = rtems_fatfs_eval_is_directory, + .eval_token = rtems_fatfs_eval_token }; + +void rtems_fatfs_eval_path( rtems_filesystem_eval_path_context_t *ctx ) +{ + rtems_filesystem_eval_path_generic( ctx, NULL, &rtems_fatfs_eval_config ); +} + +int rtems_fatfs_mknod( + const rtems_filesystem_location_info_t *parentloc, + const char *name, + size_t namelen, + mode_t mode, + dev_t dev +) +{ + char full_path[ 256 ]; + FRESULT fr; + FIL fil; + int rc; + + (void) dev; + + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) + parentloc->mt_entry->fs_info; + rtems_fatfs_node_t *parent_node = (rtems_fatfs_node_t *) + parentloc->node_access; + + rc = rtems_fatfs_build_full_path( + fs_info, + parent_node, + name, + namelen, + full_path, + sizeof( full_path ) + ); + + if ( rc != 0 ) { + return rc; + } + + rtems_fatfs_lock( parentloc->mt_entry ); + + if ( S_ISDIR( mode ) ) { + fr = f_mkdir( full_path ); + if ( fr == FR_OK ) { + rtems_fatfs_set_parent_timestamps( parent_node ); + } + } else if ( S_ISREG( mode ) ) { + fr = f_open( &fil, full_path, FA_CREATE_NEW | FA_WRITE ); + if ( fr == FR_OK ) { + f_close( &fil ); + rtems_fatfs_set_parent_timestamps( parent_node ); + } + } else { + rtems_fatfs_unlock( parentloc->mt_entry ); + rtems_set_errno_and_return_minus_one( ENOTSUP ); + } + + rtems_fatfs_unlock( parentloc->mt_entry ); + + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + return 0; +} + +int rtems_fatfs_rmnod( + const rtems_filesystem_location_info_t *parentloc, + const rtems_filesystem_location_info_t *loc +) +{ + rtems_fatfs_fs_info_t *fs_info = NULL; + rtems_fatfs_node_t *node = NULL; + char full_path[ 256 ]; + FRESULT fr; + int rc; + + fs_info = (rtems_fatfs_fs_info_t *) parentloc->mt_entry->fs_info; + node = (rtems_fatfs_node_t *) loc->node_access; + + if ( node == NULL ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + if ( node->is_open && !node->is_directory ) { + f_close( &node->handle.file ); + node->is_open = false; + } + + rc = rtems_fatfs_resolve_node_path( + fs_info, + node, + full_path, + sizeof( full_path ), + NULL + ); + if ( rc != 0 ) { + return rc; + } + + rtems_fatfs_lock( parentloc->mt_entry ); + + fr = f_unlink( full_path ); + + rtems_fatfs_unlock( parentloc->mt_entry ); + + if ( fr != FR_OK ) { + if ( ( fr == FR_DENIED || fr == FR_INVALID_NAME ) && node->is_directory ) { + errno = ENOTEMPTY; + } else { + errno = rtems_fatfs_fresult_to_errno( fr ); + } + return -1; + } + + return 0; +} + +bool rtems_fatfs_are_nodes_equal( + const rtems_filesystem_location_info_t *a, + const rtems_filesystem_location_info_t *b +) +{ + rtems_fatfs_node_t *node_a = (rtems_fatfs_node_t *) a->node_access; + rtems_fatfs_node_t *node_b = (rtems_fatfs_node_t *) b->node_access; + + if ( node_a == NULL || node_b == NULL ) { + return false; + } + + return strcmp( node_a->path, node_b->path ) == 0; +} + +int rtems_fatfs_clone_node( rtems_filesystem_location_info_t *loc ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) loc->node_access; + + if ( node != NULL ) { + rtems_fatfs_node_get( node ); + } + + return 0; +} + +void rtems_fatfs_free_node( const rtems_filesystem_location_info_t *loc ) +{ + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) loc->node_access; + + if ( node != NULL ) { + rtems_fatfs_node_put( node ); + } +} + +int rtems_fatfs_rename( + const rtems_filesystem_location_info_t *old_parent_loc, + const rtems_filesystem_location_info_t *old_loc, + const rtems_filesystem_location_info_t *new_parent_loc, + const char *new_name, + size_t new_namelen +) +{ + rtems_fatfs_fs_info_t *fs_info = NULL; + rtems_fatfs_node_t *old_node = NULL; + char old_path[ 256 ]; + char new_path[ 256 ]; + FRESULT fr; + int rc; + + fs_info = (rtems_fatfs_fs_info_t *) old_parent_loc->mt_entry->fs_info; + old_node = (rtems_fatfs_node_t *) old_loc->node_access; + + if ( old_node == NULL ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + rc = rtems_fatfs_resolve_node_path( + fs_info, + old_node, + old_path, + sizeof( old_path ), + NULL + ); + if ( rc != 0 ) { + return rc; + } + + rtems_fatfs_fs_info_t *new_fs_info = (rtems_fatfs_fs_info_t *) + new_parent_loc->mt_entry->fs_info; + rtems_fatfs_node_t *new_parent_node = (rtems_fatfs_node_t *) + new_parent_loc->node_access; + rc = rtems_fatfs_build_full_path( + new_fs_info, + new_parent_node, + new_name, + new_namelen, + new_path, + sizeof( new_path ) + ); + if ( rc != 0 ) { + return rc; + } + + rtems_fatfs_lock( old_parent_loc->mt_entry ); + + fr = f_rename( old_path, new_path ); + + rtems_fatfs_unlock( old_parent_loc->mt_entry ); + + if ( fr != FR_OK ) { + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + + return 0; +} + +static void rtems_fatfs_update_fat_timestamp( + const char *path, + time_t timestamp +) +{ + struct tm *tm_info = localtime( ×tamp ); + if ( tm_info != NULL ) { + FILINFO fno; + memset( &fno, 0, sizeof( fno ) ); + + fno.fdate = ( ( tm_info->tm_year + 1900 - 1980 ) << 9 ) | + ( ( tm_info->tm_mon + 1 ) << 5 ) | ( tm_info->tm_mday ); + fno.ftime = ( tm_info->tm_hour << 11 ) | ( tm_info->tm_min << 5 ) | + ( tm_info->tm_sec >> 1 ); + + f_utime( path, &fno ); + } +} + +int rtems_fatfs_utimens( + const rtems_filesystem_location_info_t *loc, + struct timespec times[ 2 ] +) +{ + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) + loc->mt_entry->fs_info; + rtems_fatfs_node_t *node = (rtems_fatfs_node_t *) loc->node_access; + char full_path[ 256 ]; + int rc; + + if ( node == NULL ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + time_t mtime = times[ 1 ].tv_sec; + time_t current_time = time( NULL ); + + rc = rtems_fatfs_resolve_node_path( + fs_info, + node, + full_path, + sizeof( full_path ), + NULL + ); + if ( rc != 0 ) { + return rc; + } + + rtems_fatfs_lock( loc->mt_entry ); + rtems_fatfs_update_fat_timestamp( full_path, mtime ); + rtems_fatfs_unlock( loc->mt_entry ); + + rtems_fatfs_cache_posix_times( fs_info, node->path, mtime, current_time ); + + node->posix_mtime = mtime; + node->posix_ctime = current_time; + node->posix_time_valid = true; + + return 0; +} + +/* + * Compiler will change name to rtems_fatfs_get_fattime + */ +DWORD get_fattime( void ) +{ + time_t current_time; + struct tm *tm_info; + DWORD fat_time = 0; + + current_time = time( NULL ); + tm_info = localtime( ¤t_time ); + + if ( tm_info != NULL ) { + fat_time = ( (DWORD) ( tm_info->tm_year + 1900 - 1980 ) << 25 ) | + ( (DWORD) ( tm_info->tm_mon + 1 ) << 21 ) | + ( (DWORD) tm_info->tm_mday << 16 ) | + ( (DWORD) tm_info->tm_hour << 11 ) | + ( (DWORD) tm_info->tm_min << 5 ) | + ( (DWORD) tm_info->tm_sec >> 1 ); + } else { + fat_time = ( 0 << 25 ) | ( 1 << 21 ) | ( 1 << 16 ); + } + + return fat_time; +} + +int rtems_fatfs_statvfs( + const rtems_filesystem_location_info_t *root_loc, + struct statvfs *sb +) +{ + rtems_fatfs_fs_info_t *fs_info = root_loc->mt_entry->fs_info; + FATFS *fs = &fs_info->fatfs; + FRESULT fr; + DWORD free_clusters; + FATFS *fatfs_ptr; + char drive_path[ 8 ]; + + if ( fs_info == NULL || sb == NULL ) { + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + rtems_fatfs_lock( root_loc->mt_entry ); + + drive_path[ 0 ] = '0' + fs_info->drive_number; + drive_path[ 1 ] = ':'; + drive_path[ 2 ] = '\0'; + + fr = f_getfree( drive_path, &free_clusters, &fatfs_ptr ); + if ( fr != FR_OK ) { + rtems_fatfs_unlock( root_loc->mt_entry ); + errno = rtems_fatfs_fresult_to_errno( fr ); + return -1; + } + +#if FF_MAX_SS != FF_MIN_SS + WORD sector_size = fs->ssize; +#else + WORD sector_size = FF_MAX_SS; +#endif + + sb->f_bsize = sector_size; + sb->f_frsize = fs->csize * sector_size; + sb->f_blocks = fs->n_fatent - 2; + sb->f_bfree = free_clusters; + sb->f_bavail = free_clusters; + sb->f_files = 0; + sb->f_ffree = 0; + sb->f_favail = 0; + sb->f_flag = 0; + +#if FF_USE_LFN + sb->f_namemax = FF_LFN_BUF - 1; +#else + sb->f_namemax = 12; +#endif + + rtems_fatfs_unlock( root_loc->mt_entry ); + + return 0; +} + +const rtems_filesystem_operations_table rtems_fatfs_ops = { + .lock_h = rtems_fatfs_lock, + .unlock_h = rtems_fatfs_unlock, + .eval_path_h = rtems_fatfs_eval_path, + .link_h = rtems_filesystem_default_link, + .are_nodes_equal_h = rtems_fatfs_are_nodes_equal, + .mknod_h = rtems_fatfs_mknod, + .rmnod_h = rtems_fatfs_rmnod, + .fchmod_h = rtems_filesystem_default_fchmod, + .chown_h = rtems_filesystem_default_chown, + .clonenod_h = rtems_fatfs_clone_node, + .freenod_h = rtems_fatfs_free_node, + .mount_h = rtems_fatfs_initialize, + .unmount_h = rtems_filesystem_default_unmount, + .fsunmount_me_h = rtems_fatfs_fsunmount_me, + .utimens_h = rtems_fatfs_utimens, + .symlink_h = rtems_filesystem_default_symlink, + .readlink_h = rtems_filesystem_default_readlink, + .rename_h = rtems_fatfs_rename, + .statvfs_h = rtems_fatfs_statvfs +}; diff --git a/cpukit/libfs/src/fatfs/rtems-fatfs.h b/cpukit/libfs/src/fatfs/rtems-fatfs.h new file mode 100644 index 0000000000..1c361f572c --- /dev/null +++ b/cpukit/libfs/src/fatfs/rtems-fatfs.h @@ -0,0 +1,372 @@ +/** + * @file + * + * @ingroup FatFS + * + * @brief RTEMS FatFS headers + */ + +/* + Copyright (C) 2025 Sepehr Ganji +*/ + +#ifndef RTEMS_FATFS_H +#define RTEMS_FATFS_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ff.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DT_REG 8 +#define DT_DIR 4 + +typedef struct { + int fd; + rtems_disk_device *dd; + bool initialized; +} fatfs_volume_t; + +#define RTEMS_FATFS_POSIX_CACHE_SIZE 64 + +typedef struct { + char path[ 256 ]; + time_t mtime; + time_t ctime; + bool valid; +} rtems_fatfs_posix_cache_entry_t; + +typedef struct { + FATFS fatfs; + rtems_recursive_mutex vol_mutex; + const rtems_filesystem_file_handlers_r *file_handlers; + const rtems_filesystem_file_handlers_r *dir_handlers; + uint8_t drive_number; + char mount_path[ 256 ]; + char current_dir[ 256 ]; + rtems_fatfs_posix_cache_entry_t posix_cache[ RTEMS_FATFS_POSIX_CACHE_SIZE ]; + int posix_cache_next; +} rtems_fatfs_fs_info_t; + +typedef struct { + union { + FIL file; + DIR dir; + } handle; + FILINFO info; + bool is_directory; + bool is_open; + char path[ 256 ]; + bool is_root_node; + int ref_count; + time_t posix_mtime; + time_t posix_ctime; + bool posix_time_valid; +} rtems_fatfs_node_t; + +/* Disk I/O interface */ +int fatfs_diskio_register_device( uint8_t pdrv, const char *device_path ); +void fatfs_diskio_unregister_device( uint8_t pdrv ); + +/* Utility functions */ +static inline int rtems_fatfs_get_full_path( + const rtems_fatfs_fs_info_t *fs_info, + const char *relative_path, + char *full_path, + size_t path_size +) +{ + size_t mount_len = strlen( fs_info->mount_path ); + size_t rel_len = strlen( relative_path ); + + if ( mount_len + rel_len >= path_size ) { + rtems_set_errno_and_return_minus_one( ENAMETOOLONG ); + } + + memcpy( full_path, fs_info->mount_path, mount_len ); + memcpy( full_path + mount_len, relative_path, rel_len + 1 ); + + return 0; +} + +static inline int rtems_fatfs_fresult_to_errno( FRESULT fr ) +{ + switch ( fr ) { + case FR_OK: + return 0; + case FR_DISK_ERR: + case FR_INT_ERR: + case FR_NOT_READY: + return EIO; + case FR_NO_FILE: + case FR_NO_PATH: + return ENOENT; + case FR_INVALID_NAME: + return EINVAL; + case FR_DENIED: + return EACCES; + case FR_EXIST: + return EEXIST; + case FR_INVALID_OBJECT: + return EBADF; + case FR_WRITE_PROTECTED: + return EROFS; + case FR_INVALID_DRIVE: + case FR_NOT_ENABLED: + case FR_NO_FILESYSTEM: + return ENXIO; + case FR_MKFS_ABORTED: + return EINVAL; + case FR_TIMEOUT: + return ETIMEDOUT; + case FR_LOCKED: + return EBUSY; + case FR_NOT_ENOUGH_CORE: + return ENOMEM; + case FR_TOO_MANY_OPEN_FILES: + return EMFILE; + case FR_INVALID_PARAMETER: + return EINVAL; + default: + return EIO; + } +} + +static inline void rtems_fatfs_unlock( + const rtems_filesystem_mount_table_entry_t *mt_entry +) +{ + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) mt_entry->fs_info; + rtems_recursive_mutex_unlock( &fs_info->vol_mutex ); +} + +static inline int rtems_fatfs_resolve_node_path( + const rtems_fatfs_fs_info_t *fs_info, + const rtems_fatfs_node_t *node, + char *full_path, + size_t path_size, + const rtems_filesystem_mount_table_entry_t *mt_entry +) +{ + int + rc = rtems_fatfs_get_full_path( fs_info, node->path, full_path, path_size ); + if ( rc != 0 && mt_entry != NULL ) { + rtems_fatfs_unlock( mt_entry ); + } + return rc; +} + +static inline void rtems_fatfs_filinfo_to_stat( + const FILINFO *fno, + struct stat *st +) +{ + const char *name = fno->fname; + ino_t inode_hash = 1; + + memset( st, 0, sizeof( *st ) ); + + while ( *name != '\0' ) { + inode_hash = ( inode_hash * 33 ) + (unsigned char) *name; + name++; + } + inode_hash ^= fno->fsize ^ ( fno->fdate << 16 ) ^ fno->ftime; + + st->st_ino = ( inode_hash != 0 ) ? inode_hash : 1; + st->st_dev = 1; + st->st_size = (off_t) fno->fsize; + st->st_blksize = 512; + st->st_blocks = ( st->st_size + 511 ) / 512; + + if ( fno->fdate != 0 || fno->ftime != 0 ) { + struct tm tm; + memset( &tm, 0, sizeof( tm ) ); + + tm.tm_year = ( ( fno->fdate >> 9 ) & 0x7F ) + 80; + tm.tm_mon = ( ( fno->fdate >> 5 ) & 0x0F ) - 1; + tm.tm_mday = fno->fdate & 0x1F; + tm.tm_hour = ( fno->ftime >> 11 ) & 0x1F; + tm.tm_min = ( fno->ftime >> 5 ) & 0x3F; + tm.tm_sec = ( fno->ftime & 0x1F ) * 2; + + st->st_mtime = mktime( &tm ); + st->st_ctime = st->st_mtime; + st->st_atime = st->st_mtime; + } + + if ( fno->fattrib & AM_DIR ) { + st->st_mode = S_IFDIR | 0755; + } else { + st->st_mode = S_IFREG | 0644; + } + + if ( fno->fattrib & AM_RDO ) { + st->st_mode &= ~( S_IWUSR | S_IWGRP | S_IWOTH ); + } +} + +static inline void rtems_fatfs_node_to_stat_basic( + const rtems_fatfs_node_t *node, + struct stat *st +) +{ + rtems_fatfs_filinfo_to_stat( &node->info, st ); +} + +static inline void rtems_fatfs_node_to_stat( + const rtems_fatfs_node_t *node, + struct stat *st, + rtems_fatfs_fs_info_t *fs_info +) +{ + rtems_fatfs_filinfo_to_stat( &node->info, st ); + + if ( fs_info != NULL ) { + blksize_t cluster_size = fs_info->fatfs.csize * 512; + st->st_blksize = cluster_size; + blkcnt_t clusters_used = ( st->st_size + cluster_size - 1 ) / cluster_size; + st->st_blocks = clusters_used * fs_info->fatfs.csize; + } + + if ( node->posix_time_valid ) { + st->st_mtime = node->posix_mtime; + st->st_ctime = node->posix_ctime; + st->st_atime = node->posix_mtime; + } else if ( fs_info != NULL ) { + for ( int i = 0; i < RTEMS_FATFS_POSIX_CACHE_SIZE; i++ ) { + if ( fs_info->posix_cache[ i ].valid && + strcmp( fs_info->posix_cache[ i ].path, node->path ) == 0 ) { + st->st_mtime = fs_info->posix_cache[ i ].mtime; + st->st_ctime = fs_info->posix_cache[ i ].ctime; + st->st_atime = fs_info->posix_cache[ i ].mtime; + break; + } + } + } +} + +static inline void rtems_fatfs_lock( + const rtems_filesystem_mount_table_entry_t *mt_entry +) +{ + rtems_fatfs_fs_info_t *fs_info = (rtems_fatfs_fs_info_t *) mt_entry->fs_info; + rtems_recursive_mutex_lock( &fs_info->vol_mutex ); +} + +/* Filesystem operations - declared in rtems-fatfs-init.c */ +int rtems_fatfs_initialize( + rtems_filesystem_mount_table_entry_t *mt_entry, + const void *data +); + +void rtems_fatfs_fsunmount_me( rtems_filesystem_mount_table_entry_t *mt_entry ); + +void rtems_fatfs_eval_path( rtems_filesystem_eval_path_context_t *ctx ); + +int rtems_fatfs_mknod( + const rtems_filesystem_location_info_t *parentloc, + const char *name, + size_t namelen, + mode_t mode, + dev_t dev +); + +int rtems_fatfs_rmnod( + const rtems_filesystem_location_info_t *parentloc, + const rtems_filesystem_location_info_t *loc +); + +int rtems_fatfs_rename( + const rtems_filesystem_location_info_t *old_parent_loc, + const rtems_filesystem_location_info_t *old_loc, + const rtems_filesystem_location_info_t *new_parent_loc, + const char *new_name, + size_t new_namelen +); + +int rtems_fatfs_utimens( + const rtems_filesystem_location_info_t *loc, + struct timespec times[ 2 ] +); + +int rtems_fatfs_statvfs( + const rtems_filesystem_location_info_t *root_loc, + struct statvfs *sb +); + +bool rtems_fatfs_are_nodes_equal( + const rtems_filesystem_location_info_t *a, + const rtems_filesystem_location_info_t *b +); + +int rtems_fatfs_clone_node( rtems_filesystem_location_info_t *loc ); + +void rtems_fatfs_free_node( const rtems_filesystem_location_info_t *loc ); + +/* File operations - declared in rtems-fatfs-file.c */ +ssize_t rtems_fatfs_file_read( rtems_libio_t *iop, void *buffer, size_t count ); + +ssize_t rtems_fatfs_file_write( + rtems_libio_t *iop, + const void *buffer, + size_t count +); + +off_t rtems_fatfs_file_lseek( rtems_libio_t *iop, off_t offset, int whence ); + +int rtems_fatfs_file_fstat( + const rtems_filesystem_location_info_t *loc, + struct stat *buf +); + +int rtems_fatfs_file_ftruncate( rtems_libio_t *iop, off_t length ); + +int rtems_fatfs_file_fsync( rtems_libio_t *iop ); + +/* Directory operations - declared in rtems-fatfs-dir.c */ +ssize_t rtems_fatfs_dir_read( rtems_libio_t *iop, void *buffer, size_t count ); + +int rtems_fatfs_dir_fstat( + const rtems_filesystem_location_info_t *loc, + struct stat *buf +); + +int rtems_fatfs_opendir( + rtems_libio_t *iop, + const char *path, + int oflag, + mode_t mode +); + +int rtems_fatfs_closedir( rtems_libio_t *iop ); + +int rtems_fatfs_openfile( + rtems_libio_t *iop, + const char *path, + int oflag, + mode_t mode +); + +int rtems_fatfs_closefile( rtems_libio_t *iop ); + +/* Handler tables */ +extern const rtems_filesystem_operations_table rtems_fatfs_ops; +extern const rtems_filesystem_file_handlers_r rtems_fatfs_file_handlers; +extern const rtems_filesystem_file_handlers_r rtems_fatfs_dir_handlers; + +#ifdef __cplusplus +} +#endif + +#endif /* RTEMS_FATFS_H */ diff --git a/spec/build/cpukit/librtemscpu.yml b/spec/build/cpukit/librtemscpu.yml index 48793bc850..1a76927542 100644 --- a/spec/build/cpukit/librtemscpu.yml +++ b/spec/build/cpukit/librtemscpu.yml @@ -4,7 +4,30 @@ cflags: - ${COVERAGE_COMPILER_FLAGS} copyrights: - Copyright (C) 2020 embedded brains GmbH & Co. KG -cppflags: [] +cppflags: +- -Ddisk_status=rtems_fatfs_disk_status +- -Ddisk_initialize=rtems_fatfs_disk_initialize +- -Ddisk_read=rtems_fatfs_disk_read +- -Ddisk_write=rtems_fatfs_disk_write +- -Ddisk_ioctl=rtems_fatfs_disk_ioctl +- -Dget_fattime=rtems_fatfs_get_fattime +- -DFFCONF_DEF=5385 +- -DFF_VOLUMES=1 +- -DFF_USE_MKFS=1 +- -DFF_USE_CHMOD=1 +- -DFF_USE_LFN=1 +- -DFF_FS_RPATH=2 +- -DFF_FS_EXFAT=1 +- -DFF_MAX_SS=512 +- -DFF_MIN_SS=512 +- -DFF_LFN_BUF=255 +- -DFF_SFN_BUF=12 +- -DFF_MAX_LFN=255 +- -DFF_USE_LABEL=0 +- -DFF_STR_VOLUME_ID=0 +- -DFF_MULTI_PARTITION=0 +- -DFF_FS_READONLY=0 +- -DFF_LBA64=0 cxxflags: - ${COVERAGE_COMPILER_FLAGS} enabled-by: true @@ -118,6 +141,7 @@ install: - cpukit/include/rtems/devzero.h - cpukit/include/rtems/diskdevs.h - cpukit/include/rtems/dosfs.h + - cpukit/include/rtems/fatfs.h - cpukit/include/rtems/dumpbuf.h - cpukit/include/rtems/endian.h - cpukit/include/rtems/error.h @@ -861,6 +885,13 @@ source: - cpukit/libfs/src/dosfs/msdos_rename.c - cpukit/libfs/src/dosfs/msdos_rmnod.c - cpukit/libfs/src/dosfs/msdos_statvfs.c +- cpukit/libfs/src/fatfs/ff.c +- cpukit/libfs/src/fatfs/ffunicode.c +- cpukit/libfs/src/fatfs/ffsystem.c +- cpukit/libfs/src/fatfs/rtems-diskio.c +- cpukit/libfs/src/fatfs/rtems-fatfs-init.c +- cpukit/libfs/src/fatfs/rtems-fatfs-file.c +- cpukit/libfs/src/fatfs/rtems-fatfs-dir.c - cpukit/libfs/src/imfs/deviceio.c - cpukit/libfs/src/imfs/imfs_add_node.c - cpukit/libfs/src/imfs/imfs_chown.c diff --git a/spec/build/testsuites/fstests/fatfsfsclose01.yml b/spec/build/testsuites/fstests/fatfsfsclose01.yml new file mode 100644 index 0000000000..dcf7d8bea1 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsclose01.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsclose01/init.c +stlib: [] +target: testsuites/fstests/fatfs_fsclose01.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfserror.yml b/spec/build/testsuites/fstests/fatfsfserror.yml new file mode 100644 index 0000000000..8a7fca5757 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfserror.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fserror/test.c +stlib: [] +target: testsuites/fstests/fatfs_fserror.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfspatheval.yml b/spec/build/testsuites/fstests/fatfsfspatheval.yml new file mode 100644 index 0000000000..6267cc7a19 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfspatheval.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fspatheval/test.c +stlib: [] +target: testsuites/fstests/fatfs_fspatheval.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsrdwr.yml b/spec/build/testsuites/fstests/fatfsfsrdwr.yml new file mode 100644 index 0000000000..c804a86691 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsrdwr.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsrdwr/init.c +stlib: [] +target: testsuites/fstests/fatfs_fsrdwr.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsrename.yml b/spec/build/testsuites/fstests/fatfsfsrename.yml new file mode 100644 index 0000000000..1d8af70f2e --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsrename.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsrename/test.c +stlib: [] +target: testsuites/fstests/fatfs_fsrename.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsrenameexisting.yml b/spec/build/testsuites/fstests/fatfsfsrenameexisting.yml new file mode 100644 index 0000000000..1374d096cf --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsrenameexisting.yml @@ -0,0 +1,22 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: +- -DTEST_STATE_EXPECTED_FAIL=1 +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsrenameexisting/test.c +stlib: [] +target: testsuites/fstests/fatfs_fsrenameexisting.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsrenamelongname.yml b/spec/build/testsuites/fstests/fatfsfsrenamelongname.yml new file mode 100644 index 0000000000..0486945189 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsrenamelongname.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsrenamelongname/test.c +stlib: [] +target: testsuites/fstests/fatfs_fsrenamelongname.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsrenamemaxlinks.yml b/spec/build/testsuites/fstests/fatfsfsrenamemaxlinks.yml new file mode 100644 index 0000000000..994462b1ce --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsrenamemaxlinks.yml @@ -0,0 +1,22 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: +- -DTEST_STATE_EXPECTED_FAIL=1 +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsrenamemaxlinks/test.c +stlib: [] +target: testsuites/fstests/fatfs_fsrenamemaxlinks.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsrmdirparent.yml b/spec/build/testsuites/fstests/fatfsfsrmdirparent.yml new file mode 100644 index 0000000000..f22ae9498f --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsrmdirparent.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsrmdirparent/test.c +stlib: [] +target: testsuites/fstests/fatfs_fsrmdirparent.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsscandir01.yml b/spec/build/testsuites/fstests/fatfsfsscandir01.yml new file mode 100644 index 0000000000..a3da5c6424 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsscandir01.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsscandir01/init.c +stlib: [] +target: testsuites/fstests/fatfs_fsscandir01.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfsstatvfs.yml b/spec/build/testsuites/fstests/fatfsfsstatvfs.yml new file mode 100644 index 0000000000..869d272503 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfsstatvfs.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fsstatvfs/test.c +stlib: [] +target: testsuites/fstests/fatfs_fsstatvfs.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fatfsfstime.yml b/spec/build/testsuites/fstests/fatfsfstime.yml new file mode 100644 index 0000000000..efe9b489c2 --- /dev/null +++ b/spec/build/testsuites/fstests/fatfsfstime.yml @@ -0,0 +1,21 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: +- testsuites/fstests/fatfs_support +ldflags: [] +links: [] +source: +- testsuites/fstests/fstime/test.c +stlib: [] +target: testsuites/fstests/fatfs_fstime.exe +type: build +use-after: [] +use-before: +- testfatfs diff --git a/spec/build/testsuites/fstests/fsfatfsformat01.yml b/spec/build/testsuites/fstests/fsfatfsformat01.yml new file mode 100644 index 0000000000..0c9924d9ba --- /dev/null +++ b/spec/build/testsuites/fstests/fsfatfsformat01.yml @@ -0,0 +1,19 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: [] +ldflags: [] +links: [] +source: +- testsuites/fstests/fsfatfsformat01/init.c +stlib: [] +target: testsuites/fstests/fsfatfsformat01.exe +type: build +use-after: [] +use-before: [] diff --git a/spec/build/testsuites/fstests/fsfatfssync01.yml b/spec/build/testsuites/fstests/fsfatfssync01.yml new file mode 100644 index 0000000000..d0014fcda1 --- /dev/null +++ b/spec/build/testsuites/fstests/fsfatfssync01.yml @@ -0,0 +1,19 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: test-program +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +features: c cprogram +includes: [] +ldflags: [] +links: [] +source: +- testsuites/fstests/fsfatfssync01/init.c +stlib: [] +target: testsuites/fstests/fsfatfssync01.exe +type: build +use-after: [] +use-before: [] diff --git a/spec/build/testsuites/fstests/grp.yml b/spec/build/testsuites/fstests/grp.yml index 02ebf6b841..bd64ad0e83 100644 --- a/spec/build/testsuites/fstests/grp.yml +++ b/spec/build/testsuites/fstests/grp.yml @@ -27,6 +27,8 @@ links: uid: libmimfs - role: build-dependency uid: librfs +- role: build-dependency + uid: libfatfs - role: build-dependency uid: fsbdpart01 - role: build-dependency @@ -41,6 +43,8 @@ links: uid: fsdosfssync01 - role: build-dependency uid: fsdosfswrite01 +- role: build-dependency + uid: fsfatfssync01 - role: build-dependency uid: fsfseeko01 - role: build-dependency @@ -221,6 +225,32 @@ links: uid: mrfsfstime - role: build-dependency uid: tftpfs +- role: build-dependency + uid: fsfatfsformat01 +- role: build-dependency + uid: fatfsfsrdwr +- role: build-dependency + uid: fatfsfserror +- role: build-dependency + uid: fatfsfspatheval +- role: build-dependency + uid: fatfsfsrename +- role: build-dependency + uid: fatfsfsrenameexisting +- role: build-dependency + uid: fatfsfsrenamelongname +- role: build-dependency + uid: fatfsfsrenamemaxlinks +- role: build-dependency + uid: fatfsfsrmdirparent +- role: build-dependency + uid: fatfsfsscandir01 +- role: build-dependency + uid: fatfsfsstatvfs +- role: build-dependency + uid: fatfsfstime +- role: build-dependency + uid: fatfsfsclose01 type: build use-after: [] use-before: diff --git a/spec/build/testsuites/fstests/libfatfs.yml b/spec/build/testsuites/fstests/libfatfs.yml new file mode 100644 index 0000000000..ed42291d3c --- /dev/null +++ b/spec/build/testsuites/fstests/libfatfs.yml @@ -0,0 +1,19 @@ +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause +build-type: library +cflags: [] +copyrights: +- Copyright (C) 2025 Sepehr Ganji +cppflags: [] +cxxflags: [] +enabled-by: true +includes: +- testsuites/fstests/fatfs_support +install: [] +install-path: null +links: [] +source: +- testsuites/fstests/fatfs_support/fs_support.c +- testsuites/fstests/support/fstest_support.c +- testsuites/fstests/support/ramdisk_support.c +target: testfatfs +type: build diff --git a/testsuites/fstests/fatfs_support/fs_config.h b/testsuites/fstests/fatfs_support/fs_config.h new file mode 100644 index 0000000000..c23a1363d6 --- /dev/null +++ b/testsuites/fstests/fatfs_support/fs_config.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * COPYRIGHT (c) 2025 Sepehr Ganji + * On-Line Applications Research Corporation (OAR). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __FATFS_SUPPORT_h +#define __FATFS_SUPPORT_h + +#define FILESYSTEM "FATFS" + +#endif diff --git a/testsuites/fstests/fatfs_support/fs_support.c b/testsuites/fstests/fatfs_support/fs_support.c new file mode 100644 index 0000000000..cd166ca713 --- /dev/null +++ b/testsuites/fstests/fatfs_support/fs_support.c @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * COPYRIGHT (c) 2025 Sepehr Ganji + * On-Line Applications Research Corporation (OAR). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + +#include +#include +#include + +#include "fs_config.h" +#include "fstest.h" +#include "fstest_support.h" +#include "ramdisk_support.h" + +/* Include FatFS headers - these are internal to the FatFS implementation */ +extern int fatfs_diskio_register_device( + unsigned char pdrv, + const char *device_path +); +extern void fatfs_diskio_unregister_device( unsigned char pdrv ); + +/* FatFS constants and structures */ +#define FR_OK 0 +#define FM_FAT 0x01 +#define FM_FAT32 0x02 + +typedef struct { + unsigned char fmt; + unsigned char num_fat; + unsigned int align; + unsigned int n_root; + unsigned long auto_cluster_size; +} mkfs_parm; + +typedef unsigned char FRESULT; + +/* FatFS function declaration */ +extern FRESULT f_mkfs( + const char *path, + const mkfs_parm *opt, + void *work, + unsigned int len +); + +#define BLOCK_SIZE 512 + +static const mkfs_parm fatfs_format_options = { + .fmt = FM_FAT, /* Format as FAT12/16 (auto-detect) */ + .num_fat = 2, /* Number of FAT copies */ + .align = 0, /* Auto data area alignment */ + .n_root = 512, /* Number of root directory entries for FAT12/16 */ + .auto_cluster_size = 0 /* Auto cluster size */ +}; + +static rtems_resource_snapshot before_mount; +static unsigned char fatfs_work_buffer[ 4096 ]; /* Work buffer for f_mkfs() */ + +static int fatfs_format_disk( const char *device_path ) +{ + FRESULT fr; + int rc; + + rc = fatfs_diskio_register_device( 0, device_path ); + if ( rc != 0 ) { + printf( "Device registration failed: %d\n", rc ); + return -1; + } + + fr = f_mkfs( + "0:", + &fatfs_format_options, + fatfs_work_buffer, + sizeof( fatfs_work_buffer ) + ); + if ( fr != FR_OK ) { + printf( "FatFS formatting failed: %d\n", fr ); + fatfs_diskio_unregister_device( 0 ); + return -1; + } + + fatfs_diskio_unregister_device( 0 ); + + return 0; +} + +void test_initialize_filesystem( void ) +{ + int rc = 0; + + rc = mkdir( BASE_FOR_TEST, S_IRWXU | S_IRWXG | S_IRWXO ); + rtems_test_assert( rc == 0 ); + + init_ramdisk(); + + rc = fatfs_format_disk( RAMDISK_PATH ); + rtems_test_assert( rc == 0 ); + + rtems_resource_snapshot_take( &before_mount ); + + rc = mount( + RAMDISK_PATH, + BASE_FOR_TEST, + "fatfs", + RTEMS_FILESYSTEM_READ_WRITE, + NULL + ); + if ( rc != 0 ) { + printf( "Mount failed with errno: %s\n", strerror( errno ) ); + } + rtems_test_assert( rc == 0 ); +} + +void test_shutdown_filesystem( void ) +{ + int rc = 0; + rc = unmount( BASE_FOR_TEST ); + + if ( rc != 0 ) { + printf( "Unmount failed with errno: %d (%s)\n", errno, strerror( errno ) ); + } else { + printf( "Unmount successful!\n" ); + } + rtems_test_assert( rc == 0 ); + rtems_test_assert( rtems_resource_snapshot_check( &before_mount ) ); + del_ramdisk(); +} + +/* configuration information */ + +/* drivers */ +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER + +/** + * Configure base RTEMS resources. + */ + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_MAXIMUM_SEMAPHORES 10 +#define CONFIGURE_MAXIMUM_TASKS 10 +#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 40 +#define CONFIGURE_INIT_TASK_STACK_SIZE ( 16 * 1024 ) +#define CONFIGURE_INIT_TASK_ATTRIBUTES RTEMS_FLOATING_POINT + +#define CONFIGURE_APPLICATION_NEEDS_LIBBLOCK + +#define CONFIGURE_FILESYSTEM_FATFS + +#define CONFIGURE_INIT +#include diff --git a/testsuites/fstests/fsfatfsformat01/init.c b/testsuites/fstests/fsfatfsformat01/init.c new file mode 100644 index 0000000000..0c323608a3 --- /dev/null +++ b/testsuites/fstests/fsfatfsformat01/init.c @@ -0,0 +1,445 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * Copyright (c) 2025 Sepehr Ganji + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "tmacros.h" + +const char rtems_test_name[] = "FSFATFSFORMAT 1"; + +#define MAX_PATH_LENGTH 100 +#define SECTOR_SIZE 512 +#define FAT12_MAX_CLN 4085 +#define FAT16_MAX_CLN 65525 +#define FAT12_DEFAULT_SECTORS_PER_CLUSTER 8 +#define FAT16_DEFAULT_SECTORS_PER_CLUSTER 32 + +/* FatFS constants and structures */ +#define FR_OK 0 +#define FR_INVALID_PARAMETER 19 +#define FM_FAT 0x01 +#define FM_FAT32 0x02 +#define FM_ANY 0x07 + +typedef struct { + unsigned char fmt; + unsigned char num_fat; + unsigned int align; + unsigned int n_root; + unsigned long auto_cluster_size; +} mkfs_parm; + +/* FatFS MKFS_PARM structure for proper parameter passing */ +typedef struct { + unsigned char fmt; /* Format option (FM_FAT, FM_FAT32, etc.) */ + unsigned char n_fat; /* Number of FATs */ + unsigned int align; /* Data area alignment (sector) */ + unsigned int n_root; /* Number of root directory entries */ + unsigned long au_size; /* Cluster size (byte) */ +} MKFS_PARM; + +typedef unsigned char FRESULT; + +extern int fatfs_diskio_register_device( + unsigned char pdrv, + const char *device_path +); +extern void fatfs_diskio_unregister_device( unsigned char pdrv ); +extern FRESULT f_mkfs( + const char *path, + const MKFS_PARM *opt, + void *work, + unsigned int len +); + +static unsigned char fatfs_work_buffer[ 4096 ]; + +static int fatfs_format_disk( + const char *device_path, + const mkfs_parm *format_options +) +{ + FRESULT fr; + int rc; + MKFS_PARM fatfs_opts; + + rc = fatfs_diskio_register_device( 0, device_path ); + if ( rc != 0 ) { + return -1; + } + + /* Convert test structure to FatFS structure */ + fatfs_opts.fmt = format_options->fmt; + fatfs_opts.n_fat = format_options->num_fat; + fatfs_opts.align = format_options->align; + fatfs_opts.n_root = format_options->n_root; + fatfs_opts.au_size = format_options->auto_cluster_size * + 512; /* Convert sectors to bytes */ + + fr = f_mkfs( + "0:", + &fatfs_opts, + fatfs_work_buffer, + sizeof( fatfs_work_buffer ) + ); + + fatfs_diskio_unregister_device( 0 ); + + return ( fr == FR_OK ) ? 0 : -1; +} + +static void test_disk_params( + const char *dev_name, + const char *mount_dir, + const blksize_t sector_size, + const blksize_t cluster_size, + const blkcnt_t sectors_per_cluster +) +{ + int rv; + int fildes; + struct stat stat_buff; + char file_name[ MAX_PATH_LENGTH + 1 ]; + ssize_t num_bytes; + unsigned int value = (unsigned int) -1; + + snprintf( file_name, MAX_PATH_LENGTH, "%s/file1.txt", mount_dir ); + memset( &stat_buff, 0, sizeof( stat_buff ) ); + + rv = mount( dev_name, mount_dir, "fatfs", RTEMS_FILESYSTEM_READ_WRITE, NULL ); + rtems_test_assert( 0 == rv ); + + fildes = open( + file_name, + O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH + ); + rtems_test_assert( -1 != fildes ); + + num_bytes = write( fildes, &value, sizeof( value ) ); + rtems_test_assert( sizeof( value ) == num_bytes ); + + rv = fstat( fildes, &stat_buff ); + rtems_test_assert( 0 == rv ); + rtems_test_assert( S_ISREG( stat_buff.st_mode ) ); + rtems_test_assert( sizeof( value ) == stat_buff.st_size ); + rtems_test_assert( cluster_size == stat_buff.st_blksize ); + rtems_test_assert( + sectors_per_cluster == ( stat_buff.st_blocks * sector_size / 512 ) + ); + rtems_test_assert( + ( ( ( stat_buff.st_size + cluster_size - 1 ) / cluster_size ) * + cluster_size / 512 ) == stat_buff.st_blocks + ); + rv = close( fildes ); + rtems_test_assert( 0 == rv ); + + rv = unmount( mount_dir ); + rtems_test_assert( 0 == rv ); + + rv = mount( dev_name, mount_dir, "fatfs", RTEMS_FILESYSTEM_READ_WRITE, NULL ); + rtems_test_assert( 0 == rv ); + + rv = unmount( mount_dir ); + rtems_test_assert( 0 == rv ); +} + +static void test_create_file( + const char *mount_dir, + uint32_t file_idx, + bool expect_ok +) +{ + char file_name[ MAX_PATH_LENGTH + 1 ]; + int fd; + + snprintf( + file_name, + MAX_PATH_LENGTH, + "%s/file%" PRIu32 ".txt", + mount_dir, + file_idx + ); + fd = open( + file_name, + O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH + ); + + if ( expect_ok ) { + int rv; + + rtems_test_assert( fd >= 0 ); + + rv = close( fd ); + rtems_test_assert( rv == 0 ); + } else { + rtems_test_assert( fd == -1 ); + } +} + +static void test_file_creation( + const char *dev_name, + const char *mount_dir, + const uint32_t number_of_files +) +{ + int rv; + uint32_t file_idx; + char file_name[ MAX_PATH_LENGTH + 1 ]; + + rv = mount( dev_name, mount_dir, "fatfs", RTEMS_FILESYSTEM_READ_WRITE, NULL ); + rtems_test_assert( 0 == rv ); + + for ( file_idx = 0; file_idx < number_of_files; ++file_idx ) { + test_create_file( mount_dir, file_idx, true ); + } + + test_create_file( mount_dir, file_idx, false ); + + for ( file_idx = 0; file_idx < number_of_files; ++file_idx ) { + snprintf( + file_name, + MAX_PATH_LENGTH, + "%s/file%" PRIu32 ".txt", + mount_dir, + file_idx + ); + rv = unlink( file_name ); + rtems_test_assert( 0 == rv ); + } + + rv = unmount( mount_dir ); + rtems_test_assert( 0 == rv ); +} + +static void test( void ) +{ + rtems_status_code sc; + int rv; + const char dev_name[] = "/dev/rda"; + const char mount_dir[] = "/mnt"; + mkfs_parm rqdata; + rtems_blkdev_bnum media_block_count; + + memset( &rqdata, 0, sizeof( rqdata ) ); + + rv = mkdir( mount_dir, S_IRWXU | S_IRWXG | S_IRWXO ); + rtems_test_assert( 0 == rv ); + + /* FAT12 */ + /* For 1.44 MB disks */ + sc = rtems_sparse_disk_create_and_register( + dev_name, + SECTOR_SIZE, + 64, + 2880, + 0 + ); + rtems_test_assert( RTEMS_SUCCESSFUL == sc ); + + /* Optimized for disk space */ + rqdata.fmt = FM_FAT; + rqdata.num_fat = 1; + rqdata.align = 0; + rqdata.n_root = 32; + rqdata.auto_cluster_size = 1; + rv = fatfs_format_disk( dev_name, &rqdata ); + rtems_test_assert( rv == 0 ); + test_disk_params( dev_name, mount_dir, SECTOR_SIZE, SECTOR_SIZE, 1 ); + test_file_creation( dev_name, mount_dir, rqdata.n_root ); + + /* FatFS silently clamps invalid parameters rather than returning errors, + * so we skip this test as it doesn't match FatFS behavior */ + + /* Optimized for read/write speed */ + rqdata.fmt = FM_FAT; + rqdata.num_fat = 2; + rqdata.align = 0; + rqdata.n_root = 0; + rqdata.auto_cluster_size = 8; + rv = fatfs_format_disk( dev_name, &rqdata ); + rtems_test_assert( rv == 0 ); + test_disk_params( dev_name, mount_dir, SECTOR_SIZE, SECTOR_SIZE * 8, 8 ); + + rv = unlink( dev_name ); + rtems_test_assert( rv == 0 ); + + /* Largest FAT12 disk */ + sc = rtems_sparse_disk_create_and_register( + dev_name, + SECTOR_SIZE, + 64, + ( FAT12_MAX_CLN * FAT12_DEFAULT_SECTORS_PER_CLUSTER ) - 1L, + 0 + ); + rtems_test_assert( RTEMS_SUCCESSFUL == sc ); + + /* Default parameters - let FatFS choose cluster size */ + rqdata.fmt = FM_FAT; + rqdata.num_fat = 2; + rqdata.align = 0; + rqdata.n_root = 512; + rqdata.auto_cluster_size = 0; + rv = fatfs_format_disk( dev_name, &rqdata ); + rtems_test_assert( rv == 0 ); + /* FatFS chooses cluster size based on volume size, so test actual result */ + test_disk_params( + dev_name, + mount_dir, + SECTOR_SIZE, + SECTOR_SIZE * 4, /* FatFS actually chooses 4 sectors per cluster */ + 4 + ); + + rv = unlink( dev_name ); + rtems_test_assert( rv == 0 ); + + /* FAT16 */ + sc = rtems_sparse_disk_create_and_register( + dev_name, + SECTOR_SIZE, + 1024, + ( FAT12_MAX_CLN * FAT12_DEFAULT_SECTORS_PER_CLUSTER ) + 1L, + 0 + ); + rtems_test_assert( RTEMS_SUCCESSFUL == sc ); + + /* Optimized for disk space */ + rqdata.fmt = FM_FAT; + rqdata.num_fat = 1; + rqdata.align = 0; + rqdata.n_root = 32; + rqdata.auto_cluster_size = 1; + rv = fatfs_format_disk( dev_name, &rqdata ); + rtems_test_assert( rv == 0 ); + test_disk_params( dev_name, mount_dir, SECTOR_SIZE, SECTOR_SIZE, 1 ); + + rv = unlink( dev_name ); + rtems_test_assert( rv == 0 ); + + /* FAT32 */ + sc = rtems_sparse_disk_create_and_register( + dev_name, + SECTOR_SIZE, + 1024, + ( FAT16_MAX_CLN * FAT16_DEFAULT_SECTORS_PER_CLUSTER ) + 41L, + 0 + ); + rtems_test_assert( RTEMS_SUCCESSFUL == sc ); + + /* Default parameters - FAT32 chooses larger cluster sizes */ + rqdata.fmt = FM_FAT32; + rqdata.num_fat = 2; + rqdata.align = 0; + rqdata.n_root = 0; + rqdata.auto_cluster_size = 0; + rv = fatfs_format_disk( dev_name, &rqdata ); + rtems_test_assert( rv == 0 ); + test_disk_params( dev_name, mount_dir, SECTOR_SIZE, SECTOR_SIZE * 16, 16 ); + + rv = unlink( dev_name ); + rtems_test_assert( rv == 0 ); + + /* Format some disks from 1MB up to 128GB - let FatFS choose appropriate FAT + * type */ + rqdata.fmt = FM_ANY; + rqdata.num_fat = 2; + rqdata.align = 0; + rqdata.n_root = 0; + rqdata.auto_cluster_size = 64; + for ( media_block_count = 1 * 1024 * ( 1024 / SECTOR_SIZE ); + media_block_count <= 128 * 1024 * 1024 * ( 1024 / SECTOR_SIZE ); + media_block_count *= 2 ) { + sc = rtems_sparse_disk_create_and_register( + dev_name, + SECTOR_SIZE, + 64, + media_block_count, + 0 + ); + rtems_test_assert( sc == RTEMS_SUCCESSFUL ); + + rv = fatfs_format_disk( dev_name, &rqdata ); + rtems_test_assert( rv == 0 ); + + test_disk_params( dev_name, mount_dir, SECTOR_SIZE, SECTOR_SIZE * 64, 64 ); + + rv = unlink( dev_name ); + rtems_test_assert( rv == 0 ); + } +} + +static void Init( rtems_task_argument arg ) +{ + (void) arg; + + TEST_BEGIN(); + + test(); + + TEST_END(); + rtems_test_exit( 0 ); +} + +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_LIBBLOCK + +#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 5 + +#define CONFIGURE_FILESYSTEM_FATFS + +#define CONFIGURE_MAXIMUM_TASKS 1 +#define CONFIGURE_MAXIMUM_SEMAPHORES 10 + +#define CONFIGURE_INIT_TASK_STACK_SIZE ( 32 * 1024 ) + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_INIT_TASK_ATTRIBUTES RTEMS_FLOATING_POINT + +#define CONFIGURE_BDBUF_BUFFER_MAX_SIZE ( 32 * 1024 ) + +#define CONFIGURE_INIT + +#include diff --git a/testsuites/fstests/fsfatfssync01/fsfatfssync01.doc b/testsuites/fstests/fsfatfssync01/fsfatfssync01.doc new file mode 100644 index 0000000000..e14831a172 --- /dev/null +++ b/testsuites/fstests/fsfatfssync01/fsfatfssync01.doc @@ -0,0 +1,17 @@ +This file documents the test fsfatfssync01. + +test set name: fsfatfssync01 + +test purpose: + Verify FatFS auto-sync behavior and explicit fsync() functionality. + +test description: + Tests FatFS's synchronization behavior by: + - Creating a file and verifying it persists after disk cache purge + - Writing data without explicit fsync() and verifying it persists after disk cache purge + (demonstrates FatFS auto-syncs on file close) + - Writing additional data with explicit fsync() and verifying it persists after disk cache purge + + Note: Unlike traditional POSIX filesystems, FatFS automatically calls f_sync() during + f_close(), ensuring all writes are immediately persisted to disk regardless of whether + fsync() was explicitly called. diff --git a/testsuites/fstests/fsfatfssync01/init.c b/testsuites/fstests/fsfatfssync01/init.c new file mode 100644 index 0000000000..245024450b --- /dev/null +++ b/testsuites/fstests/fsfatfssync01/init.c @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/* + * Copyright (c) 2025 Sepehr Ganji + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tmacros.h" + +const char rtems_test_name[] = "FSFATFSSYNC 1"; + +/* FatFS constants and structures */ +#define FR_OK 0 +#define FM_FAT 0x01 +#define FM_FAT32 0x02 + +typedef struct { + unsigned char fmt; + unsigned char num_fat; + unsigned int align; + unsigned int n_root; + unsigned long auto_cluster_size; +} MKFS_PARM; + +typedef unsigned char FRESULT; + +/* External FatFS functions */ +extern int fatfs_diskio_register_device( + unsigned char pdrv, + const char *device_path +); +extern void fatfs_diskio_unregister_device( unsigned char pdrv ); +extern FRESULT f_mkfs( + const char *path, + const MKFS_PARM *opt, + void *work, + unsigned int len +); + +static unsigned char fatfs_work_buffer[ 4096 ]; + +static void create_file( const char *file ) +{ + int fd; + int rv; + + fd = creat( file, S_IRWXU | S_IRWXG | S_IRWXO ); + rtems_test_assert( fd >= 0 ); + + rv = fsync( fd ); + rtems_test_assert( rv == 0 ); + + rv = close( fd ); + rtems_test_assert( rv == 0 ); +} + +static void write_to_file( const char *file, bool sync ) +{ + int fd; + char buf[ 1 ] = { 'A' }; + ssize_t n; + off_t pos_before, pos_after; + int rv; + + fd = open( file, O_RDWR ); + rtems_test_assert( fd >= 0 ); + + pos_before = lseek( fd, 0, SEEK_END ); + + n = write( fd, buf, sizeof( buf ) ); + rtems_test_assert( n == (ssize_t) sizeof( buf ) ); + + pos_after = lseek( fd, 0, SEEK_END ); + rtems_test_assert( pos_after == pos_before + sizeof( buf ) ); + + if ( sync ) { + rv = fsync( fd ); + rtems_test_assert( rv == 0 ); + } + + rv = close( fd ); + rtems_test_assert( rv == 0 ); +} + +static void check_file_size( const char *file, off_t size ) +{ + int fd; + off_t pos; + int rv; + + fd = open( file, O_RDWR ); + rtems_test_assert( fd >= 0 ); + + pos = lseek( fd, 0, SEEK_END ); + rtems_test_assert( pos == size ); + + rv = close( fd ); + rtems_test_assert( rv == 0 ); +} + +static void test( const char *rda, const char *mnt, const char *file ) +{ + static const MKFS_PARM fatfs_format_options = { + .fmt = FM_FAT, + .num_fat = 2, + .align = 0, + .n_root = 512, + .auto_cluster_size = 0 + }; + + int disk_fd; + int rv; + FRESULT fr; + + disk_fd = open( rda, O_RDWR ); + rtems_test_assert( disk_fd >= 0 ); + + rv = fatfs_diskio_register_device( 0, rda ); + rtems_test_assert( rv == 0 ); + + /* Format using FatFS */ + fr = f_mkfs( + "0:", + &fatfs_format_options, + fatfs_work_buffer, + sizeof( fatfs_work_buffer ) + ); + rtems_test_assert( fr == FR_OK ); + + fatfs_diskio_unregister_device( 0 ); + + rv = mount_and_make_target_path( + rda, + mnt, + "fatfs", + RTEMS_FILESYSTEM_READ_WRITE, + NULL + ); + rtems_test_assert( rv == 0 ); + + create_file( file ); + rv = rtems_disk_fd_purge( disk_fd ); + rtems_test_assert( rv == 0 ); + + write_to_file( file, false ); + rv = rtems_disk_fd_purge( disk_fd ); + rtems_test_assert( rv == 0 ); + check_file_size( file, 1 ); + + write_to_file( file, true ); + rv = rtems_disk_fd_purge( disk_fd ); + rtems_test_assert( rv == 0 ); + check_file_size( file, 2 ); + + rv = unmount( mnt ); + rtems_test_assert( rv == 0 ); + + rv = close( disk_fd ); + rtems_test_assert( rv == 0 ); +} + +static void Init( rtems_task_argument arg ) +{ + (void) arg; + + TEST_BEGIN(); + + test( "/dev/rda", "/mnt", "/mnt/file" ); + + TEST_END(); + rtems_test_exit( 0 ); +} + +rtems_ramdisk_config rtems_ramdisk_configuration[] = { + { .block_size = 512, .block_num = 1024 } +}; + +size_t rtems_ramdisk_configuration_size = 1; + +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER +#define CONFIGURE_APPLICATION_EXTRA_DRIVERS RAMDISK_DRIVER_TABLE_ENTRY +#define CONFIGURE_APPLICATION_NEEDS_LIBBLOCK + +#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 6 + +#define CONFIGURE_FILESYSTEM_FATFS + +#define CONFIGURE_MAXIMUM_TASKS 2 + +#define CONFIGURE_EXTRA_TASK_STACKS ( 8 * 1024 ) + +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_INIT_TASK_ATTRIBUTES RTEMS_FLOATING_POINT + +#define CONFIGURE_INIT + +#include