summaryrefslogtreecommitdiffstats
path: root/c/src/libfs/src/dosfs/fat.c
diff options
context:
space:
mode:
Diffstat (limited to 'c/src/libfs/src/dosfs/fat.c')
-rw-r--r--c/src/libfs/src/dosfs/fat.c695
1 files changed, 695 insertions, 0 deletions
diff --git a/c/src/libfs/src/dosfs/fat.c b/c/src/libfs/src/dosfs/fat.c
new file mode 100644
index 0000000000..852c104781
--- /dev/null
+++ b/c/src/libfs/src/dosfs/fat.c
@@ -0,0 +1,695 @@
+/*
+ * fat.c
+ *
+ * Low-level operations on a volume with FAT filesystem
+ *
+ * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
+ * Author: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
+ *
+ * @(#) $Id$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <rtems/libio_.h>
+
+#include "fat.h"
+
+/* _fat_block_read --
+ * This function reads 'count' bytes from device filesystem is mounted on,
+ * starts at 'start+offset' position where 'start' computed in sectors
+ * and 'offset' is offset inside sector (reading may cross sectors
+ * boundary; in this case assumed we want to read sequential sector(s))
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * start - sector num to start read from
+ * offset - offset inside sector 'start'
+ * count - count of bytes to read
+ * buff - buffer provided by user
+ *
+ * RETURNS:
+ * bytes read on success, or -1 if error occured
+ * and errno set appropriately
+ */
+ssize_t
+_fat_block_read(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 start,
+ unsigned32 offset,
+ unsigned32 count,
+ void *buff
+ )
+{
+ int rc = RC_OK;
+ register fat_fs_info_t *fs_info = mt_entry->fs_info;
+ ssize_t cmpltd = 0;
+ unsigned32 blk = start;
+ unsigned32 ofs = offset;
+ bdbuf_buffer *block = NULL;
+ unsigned32 c = 0;
+
+ while (count > 0)
+ {
+ rc = fat_buf_access(fs_info, blk, FAT_OP_TYPE_READ, &block);
+ if (rc != RC_OK)
+ return rc;
+
+ c = MIN(count, (fs_info->vol.bps - ofs));
+ memcpy((buff + cmpltd), (block->buffer + ofs), c);
+
+ count -= c;
+ cmpltd += c;
+ blk++;
+ ofs = 0;
+ }
+ return cmpltd;
+}
+
+/* _fat_block_write --
+ * This function write 'count' bytes to device filesystem is mounted on,
+ * starts at 'start+offset' position where 'start' computed in sectors
+ * and 'offset' is offset inside sector (writing may cross sectors
+ * boundary; in this case assumed we want to write sequential sector(s))
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * start - sector num to start read from
+ * offset - offset inside sector 'start'
+ * count - count of bytes to write
+ * buff - buffer provided by user
+ *
+ * RETURNS:
+ * bytes written on success, or -1 if error occured
+ * and errno set appropriately
+ */
+ssize_t
+_fat_block_write(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 start,
+ unsigned32 offset,
+ unsigned32 count,
+ const void *buff)
+{
+ int rc = RC_OK;
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+ ssize_t cmpltd = 0;
+ unsigned32 blk = start;
+ unsigned32 ofs = offset;
+ bdbuf_buffer *block = NULL;
+ unsigned32 c = 0;
+
+ while(count > 0)
+ {
+ c = MIN(count, (fs_info->vol.bps - ofs));
+
+ if (c == fs_info->vol.bps)
+ rc = fat_buf_access(fs_info, blk, FAT_OP_TYPE_GET, &block);
+ else
+ rc = fat_buf_access(fs_info, blk, FAT_OP_TYPE_READ, &block);
+ if (rc != RC_OK)
+ return rc;
+
+ memcpy((block->buffer + ofs), (buff + cmpltd), c);
+
+ fat_buf_mark_modified(fs_info);
+
+ count -= c;
+ cmpltd +=c;
+ blk++;
+ ofs = 0;
+ }
+ return cmpltd;
+}
+
+
+
+
+/* fat_cluster_read --
+ * wrapper for reading a whole cluster at once
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * cln - number of cluster to read
+ * buff - buffer provided by user
+ *
+ * RETURNS:
+ * bytes read on success, or -1 if error occured
+ * and errno set appropriately
+ */
+ssize_t
+fat_cluster_read(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 cln,
+ void *buff
+ )
+{
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+ unsigned32 fsec = 0;
+
+ fsec = fat_cluster_num_to_sector_num(mt_entry, cln);
+
+ return _fat_block_read(mt_entry, fsec, 0,
+ fs_info->vol.spc << fs_info->vol.sec_log2, buff);
+}
+
+/* fat_cluster_write --
+ * wrapper for writting a whole cluster at once
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * cln - number of cluster to write
+ * buff - buffer provided by user
+ *
+ * RETURNS:
+ * bytes written on success, or -1 if error occured
+ * and errno set appropriately
+ */
+ssize_t
+fat_cluster_write(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 cln,
+ const void *buff
+ )
+{
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+ unsigned32 fsec = 0;
+
+ fsec = fat_cluster_num_to_sector_num(mt_entry, cln);
+
+ return _fat_block_write(mt_entry, fsec, 0,
+ fs_info->vol.spc << fs_info->vol.sec_log2, buff);
+}
+
+/* fat_init_volume_info --
+ * Get inforamtion about volume on which filesystem is mounted on
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ *
+ * RETURNS:
+ * RC_OK on success, or -1 if error occured
+ * and errno set appropriately
+ */
+int
+fat_init_volume_info(rtems_filesystem_mount_table_entry_t *mt_entry)
+{
+ int rc = RC_OK;
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+ register fat_vol_t *vol = &fs_info->vol;
+ unsigned32 data_secs = 0;
+ char boot_rec[FAT_MAX_BPB_SIZE];
+ char fs_info_sector[FAT_USEFUL_INFO_SIZE];
+ ssize_t ret = 0;
+ int fd;
+ struct stat stat_buf;
+ int i = 0;
+
+ rc = stat(mt_entry->dev, &stat_buf);
+ if (rc == -1)
+ return rc;
+
+ /* rtmes feature: no block devices, all are character devices */
+ if (!S_ISCHR(stat_buf.st_mode))
+ set_errno_and_return_minus_one(ENOTBLK);
+
+ /* check that device is registred as block device and lock it */
+ vol->dd = rtems_disk_lookup(stat_buf.st_dev);
+ if (vol->dd == NULL)
+ set_errno_and_return_minus_one(ENOTBLK);
+
+ vol->dev = stat_buf.st_dev;
+
+ fd = open(mt_entry->dev, O_RDONLY);
+ if (fd == -1)
+ {
+ rtems_disk_release(vol->dd);
+ return -1;
+ }
+
+ ret = read(fd, (void *)boot_rec, FAT_MAX_BPB_SIZE);
+ if ( ret != FAT_MAX_BPB_SIZE )
+ {
+ close(fd);
+ rtems_disk_release(vol->dd);
+ set_errno_and_return_minus_one( EIO );
+ }
+ close(fd);
+
+ vol->bps = FAT_BR_BYTES_PER_SECTOR(boot_rec);
+
+ if ( (vol->bps != 512) &&
+ (vol->bps != 1024) &&
+ (vol->bps != 2048) &&
+ (vol->bps != 4096))
+ {
+ rtems_disk_release(vol->dd);
+ set_errno_and_return_minus_one( EINVAL );
+ }
+
+ for (vol->sec_mul = 0, i = (vol->bps >> FAT_SECTOR512_BITS); (i & 1) == 0;
+ i >>= 1, vol->sec_mul++);
+ for (vol->sec_log2 = 0, i = vol->bps; (i & 1) == 0;
+ i >>= 1, vol->sec_log2++);
+
+ vol->spc = FAT_BR_SECTORS_PER_CLUSTER(boot_rec);
+ for (vol->spc_log2 = 0, i = vol->spc; (i & 1) == 0;
+ i >>= 1, vol->spc_log2++);
+
+ /*
+ * According to M$ White Paper "bytes per cluster" value
+ * greater than 32K is invalid
+ */
+ if ((vol->bpc = vol->bps << vol->spc_log2) > MS_BYTES_PER_CLUSTER_LIMIT)
+ {
+ rtems_disk_release(vol->dd);
+ set_errno_and_return_minus_one(EINVAL);
+ }
+
+ for (vol->bpc_log2 = 0, i = vol->bpc; (i & 1) == 0;
+ i >>= 1, vol->bpc_log2++);
+
+ vol->fats = FAT_BR_FAT_NUM(boot_rec);
+ vol->fat_loc = FAT_BR_RESERVED_SECTORS_NUM(boot_rec);
+
+ vol->rdir_entrs = FAT_BR_FILES_PER_ROOT_DIR(boot_rec);
+
+ /* calculate the count of sectors occupied by the root directory */
+ vol->rdir_secs = ((vol->rdir_entrs * FAT_DIRENTRY_SIZE) + (vol->bps - 1)) /
+ vol->bps;
+
+ vol->rdir_size = vol->rdir_secs << vol->sec_log2;
+
+ if ( (FAT_BR_SECTORS_PER_FAT(boot_rec)) != 0)
+ vol->fat_length = FAT_BR_SECTORS_PER_FAT(boot_rec);
+ else
+ vol->fat_length = FAT_BR_SECTORS_PER_FAT32(boot_rec);
+
+ vol->data_fsec = vol->fat_loc + vol->fats * vol->fat_length +
+ vol->rdir_secs;
+
+ /* for FAT12/16 root dir starts at(sector) */
+ vol->rdir_loc = vol->fat_loc + vol->fats * vol->fat_length;
+
+ if ( (FAT_BR_TOTAL_SECTORS_NUM16(boot_rec)) != 0)
+ vol->tot_secs = FAT_BR_TOTAL_SECTORS_NUM16(boot_rec);
+ else
+ vol->tot_secs = FAT_BR_TOTAL_SECTORS_NUM32(boot_rec);
+
+ data_secs = vol->tot_secs - vol->data_fsec;
+
+ vol->data_cls = data_secs / vol->spc;
+
+ /* determine FAT type at least */
+ if ( vol->data_cls < FAT_FAT12_MAX_CLN)
+ {
+ vol->type = FAT_FAT12;
+ vol->mask = FAT_FAT12_MASK;
+ vol->eoc_val = FAT_FAT12_EOC;
+ }
+ else
+ {
+ if ( vol->data_cls < FAT_FAT16_MAX_CLN)
+ {
+ vol->type = FAT_FAT16;
+ vol->mask = FAT_FAT16_MASK;
+ vol->eoc_val = FAT_FAT16_EOC;
+ }
+ else
+ {
+ vol->type = FAT_FAT32;
+ vol->mask = FAT_FAT32_MASK;
+ vol->eoc_val = FAT_FAT32_EOC;
+ }
+ }
+
+ if (vol->type == FAT_FAT32)
+ {
+ vol->rdir_cl = FAT_BR_FAT32_ROOT_CLUSTER(boot_rec);
+
+ vol->mirror = FAT_BR_EXT_FLAGS(boot_rec) & FAT_BR_EXT_FLAGS_MIRROR;
+ if (vol->mirror)
+ vol->afat = FAT_BR_EXT_FLAGS(boot_rec) & FAT_BR_EXT_FLAGS_FAT_NUM;
+ else
+ vol->afat = 0;
+
+ vol->info_sec = FAT_BR_FAT32_FS_INFO_SECTOR(boot_rec);
+ if( vol->info_sec == 0 )
+ {
+ rtems_disk_release(vol->dd);
+ set_errno_and_return_minus_one( EINVAL );
+ }
+ else
+ {
+ ret = _fat_block_read(mt_entry, vol->info_sec , 0,
+ FAT_FSI_LEADSIG_SIZE, fs_info_sector);
+ if ( ret < 0 )
+ {
+ rtems_disk_release(vol->dd);
+ return -1;
+ }
+
+ if (FAT_FSINFO_LEAD_SIGNATURE(fs_info_sector) !=
+ FAT_FSINFO_LEAD_SIGNATURE_VALUE)
+ {
+ rtems_disk_release(vol->dd);
+ set_errno_and_return_minus_one( EINVAL );
+ }
+ else
+ {
+ ret = _fat_block_read(mt_entry, vol->info_sec , FAT_FSI_INFO,
+ FAT_USEFUL_INFO_SIZE, fs_info_sector);
+ if ( ret < 0 )
+ {
+ rtems_disk_release(vol->dd);
+ return -1;
+ }
+
+ vol->free_cls = FAT_FSINFO_FREE_CLUSTER_COUNT(fs_info_sector);
+ vol->next_cl = FAT_FSINFO_NEXT_FREE_CLUSTER(fs_info_sector);
+ rc = fat_fat32_update_fsinfo_sector(mt_entry, 0xFFFFFFFF,
+ 0xFFFFFFFF);
+ if ( rc != RC_OK )
+ {
+ rtems_disk_release(vol->dd);
+ return rc;
+ }
+ }
+ }
+ }
+ else
+ {
+ vol->rdir_cl = 0;
+ vol->mirror = 0;
+ vol->afat = 0;
+ vol->free_cls = 0xFFFFFFFF;
+ vol->next_cl = 0xFFFFFFFF;
+ }
+ vol->afat_loc = vol->fat_loc + vol->fat_length * vol->afat;
+
+ /* set up collection of fat-files fd */
+ fs_info->vhash = calloc(FAT_HASH_SIZE, sizeof(Chain_Control));
+ if ( fs_info->vhash == NULL )
+ {
+ rtems_disk_release(vol->dd);
+ set_errno_and_return_minus_one( ENOMEM );
+ }
+
+ for (i = 0; i < FAT_HASH_SIZE; i++)
+ _Chain_Initialize_empty(fs_info->vhash + i);
+
+ fs_info->rhash = calloc(FAT_HASH_SIZE, sizeof(Chain_Control));
+ if ( fs_info->rhash == NULL )
+ {
+ rtems_disk_release(vol->dd);
+ free(fs_info->vhash);
+ set_errno_and_return_minus_one( ENOMEM );
+ }
+ for (i = 0; i < FAT_HASH_SIZE; i++)
+ _Chain_Initialize_empty(fs_info->rhash + i);
+
+ fs_info->uino_pool_size = FAT_UINO_POOL_INIT_SIZE;
+ fs_info->uino_base = (vol->tot_secs << vol->sec_mul) << 4;
+ fs_info->index = 0;
+ fs_info->uino = (char *)calloc(fs_info->uino_pool_size, sizeof(char));
+ if ( fs_info->uino == NULL )
+ {
+ rtems_disk_release(vol->dd);
+ free(fs_info->vhash);
+ free(fs_info->rhash);
+ set_errno_and_return_minus_one( ENOMEM );
+ }
+ fs_info->sec_buf = (char *)calloc(vol->bps, sizeof(char));
+ if (fs_info->sec_buf == NULL)
+ {
+ rtems_disk_release(vol->dd);
+ free(fs_info->vhash);
+ free(fs_info->rhash);
+ free(fs_info->uino);
+ set_errno_and_return_minus_one( ENOMEM );
+ }
+
+ return RC_OK;
+}
+
+/* fat_shutdown_drive --
+ * Free all allocated resources and synchronize all necessary data
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ *
+ * RETURNS:
+ * RC_OK on success, or -1 if error occured
+ * and errno set appropriately
+ */
+int
+fat_shutdown_drive(rtems_filesystem_mount_table_entry_t *mt_entry)
+{
+ int rc = RC_OK;
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+ int i = 0;
+
+ if (fs_info->vol.type & FAT_FAT32)
+ {
+ rc = fat_fat32_update_fsinfo_sector(mt_entry, fs_info->vol.free_cls,
+ fs_info->vol.next_cl);
+ if ( rc != RC_OK )
+ rc = -1;
+ }
+
+ fat_buf_release(fs_info);
+
+ if (rtems_bdbuf_syncdev(fs_info->vol.dev) != RTEMS_SUCCESSFUL)
+ rc = -1;
+
+ for (i = 0; i < FAT_HASH_SIZE; i++)
+ {
+ Chain_Node *node = NULL;
+ Chain_Control *the_chain = fs_info->vhash + i;
+
+ while ( (node = _Chain_Get(the_chain)) != NULL )
+ free(node);
+ }
+
+ for (i = 0; i < FAT_HASH_SIZE; i++)
+ {
+ Chain_Node *node = NULL;
+ Chain_Control *the_chain = fs_info->rhash + i;
+
+ while ( (node = _Chain_Get(the_chain)) != NULL )
+ free(node);
+ }
+
+ free(fs_info->vhash);
+ free(fs_info->rhash);
+
+ free(fs_info->uino);
+ free(fs_info->sec_buf);
+ rtems_disk_release(fs_info->vol.dd);
+
+ if (rc)
+ errno = EIO;
+ return rc;
+}
+
+/* fat_init_clusters_chain --
+ * Zeroing contents of all clusters in the chain
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * start_cluster_num - num of first cluster in the chain
+ *
+ * RETURNS:
+ * RC_OK on success, or -1 if error occured
+ * and errno set appropriately
+ */
+int
+fat_init_clusters_chain(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 start_cln
+ )
+{
+ int rc = RC_OK;
+ ssize_t ret = 0;
+ register fat_fs_info_t *fs_info = mt_entry->fs_info;
+ unsigned32 cur_cln = start_cln;
+ char *buf;
+
+ buf = calloc(fs_info->vol.bpc, sizeof(char));
+ if ( buf == NULL )
+ set_errno_and_return_minus_one( EIO );
+
+ while ((cur_cln & fs_info->vol.mask) != fs_info->vol.eoc_val)
+ {
+ ret = fat_cluster_write(mt_entry, cur_cln, buf);
+ if ( ret == -1 )
+ {
+ free(buf);
+ return -1;
+ }
+
+ rc = fat_get_fat_cluster(mt_entry, cur_cln, &cur_cln);
+ if ( rc != RC_OK )
+ {
+ free(buf);
+ return rc;
+ }
+
+ }
+ free(buf);
+ return rc;
+}
+
+#define FAT_UNIQ_INO_BASE 0x0FFFFF00
+
+#define FAT_UNIQ_INO_IS_BUSY(index, arr) \
+ (((arr)[((index)>>3)]>>((index) & (8-1))) & 0x01)
+
+#define FAT_SET_UNIQ_INO_BUSY(index, arr) \
+ ((arr)[((index)>>3)] |= (0x01<<((index) & (8-1))))
+
+#define FAT_SET_UNIQ_INO_FREE(index, arr) \
+ ((arr)[((index)>>3)] &= (~(0x01<<((index) & (8-1)))))
+
+/* fat_get_unique_ino --
+ * Allocate unique ino from unique ino pool
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ *
+ * RETURNS:
+ * unique inode number on success, or 0 if there is no free unique inode
+ * number in the pool
+ *
+ * ATTENTION:
+ * 0 means FAILED !!!
+ *
+ */
+unsigned32
+fat_get_unique_ino(rtems_filesystem_mount_table_entry_t *mt_entry)
+{
+ register fat_fs_info_t *fs_info = mt_entry->fs_info;
+ unsigned32 j = 0;
+ rtems_boolean resrc_unsuff = FALSE;
+
+ while (!resrc_unsuff)
+ {
+ for (j = 0; j < fs_info->uino_pool_size; j++)
+ {
+ if (!FAT_UNIQ_INO_IS_BUSY(fs_info->index, fs_info->uino))
+ {
+ FAT_SET_UNIQ_INO_BUSY(fs_info->index, fs_info->uino);
+ return (fs_info->uino_base + fs_info->index);
+ }
+ fs_info->index++;
+ if (fs_info->index >= fs_info->uino_pool_size)
+ fs_info->index = 0;
+ }
+
+ if ((fs_info->uino_pool_size << 1) < (0x0FFFFFFF - fs_info->uino_base))
+ {
+ fs_info->uino_pool_size <<= 1;
+ fs_info->uino = realloc(fs_info->uino, fs_info->uino_pool_size);
+ if (fs_info->uino != NULL)
+ fs_info->index = fs_info->uino_pool_size;
+ else
+ resrc_unsuff = TRUE;
+ }
+ else
+ resrc_unsuff = TRUE;
+ }
+ return 0;
+}
+
+/* fat_free_unique_ino --
+ * Return unique ino to unique ino pool
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * ino - inode number to free
+ *
+ * RETURNS:
+ * None
+ */
+void
+fat_free_unique_ino(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 ino
+ )
+{
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+
+ FAT_SET_UNIQ_INO_FREE((ino - fs_info->uino_base), fs_info->uino);
+}
+
+/* fat_ino_is_unique --
+ * Test whether ino is from unique ino pool
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * ino - ino to be tested
+ *
+ * RETURNS:
+ * TRUE if ino is allocated from unique ino pool, FALSE otherwise
+ */
+inline rtems_boolean
+fat_ino_is_unique(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 ino
+ )
+{
+ fat_fs_info_t *fs_info = mt_entry->fs_info;
+
+ return (ino >= fs_info->uino_base);
+}
+
+/* fat_fat32_update_fsinfo_sector --
+ * Synchronize fsinfo sector for FAT32 volumes
+ *
+ * PARAMETERS:
+ * mt_entry - mount table entry
+ * free_count - count of free clusters
+ * next_free - the next free cluster num
+ *
+ * RETURNS:
+ * RC_OK on success, or -1 if error occured (errno set appropriately)
+ */
+int
+fat_fat32_update_fsinfo_sector(
+ rtems_filesystem_mount_table_entry_t *mt_entry,
+ unsigned32 free_count,
+ unsigned32 next_free
+ )
+{
+ ssize_t ret1 = 0, ret2 = 0;
+ register fat_fs_info_t *fs_info = mt_entry->fs_info;
+ unsigned32 le_free_count = 0;
+ unsigned32 le_next_free = 0;
+
+ le_free_count = CT_LE_L(free_count);
+ le_next_free = CT_LE_L(next_free);
+
+ ret1 = _fat_block_write(mt_entry,
+ fs_info->vol.info_sec,
+ FAT_FSINFO_FREE_CLUSTER_COUNT_OFFSET,
+ 4,
+ (char *)(&le_free_count));
+
+ ret2 = _fat_block_write(mt_entry,
+ fs_info->vol.info_sec,
+ FAT_FSINFO_NEXT_FREE_CLUSTER_OFFSET,
+ 4,
+ (char *)(&le_next_free));
+
+ if ( (ret1 < 0) || (ret2 < 0) )
+ return -1;
+
+ return RC_OK;
+}
+ \ No newline at end of file