sos-code-article10/drivers/fs_fat.c

3213 lines
98 KiB
C

/* Copyright (C) 2007 Anthoine Bourgeois
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/kmalloc.h>
#include <sos/klibc.h>
#include <sos/list.h>
#include <sos/assert.h>
#include <drivers/bochs.h>
#include <sos/physmem.h>
#include <sos/uaccess.h>
#include <sos/fs.h>
#include <sos/fs_nscache.h>
/**
* This drivers implements the FAT file system.
*
* The FAT file system is integrated in the VFS and allow on SOS the
* capability of recording datas on a disk.
* All VFS operations (mount, umount, read, write, seek, stat, mmap...) are
* implemented in this file.
*
* This drivers haves few limitations:
* - FAT12 not supported
* - no time stamp on nodes
* - no long directory entries
* - the 'rename' VFS operation is not supported
* Others features should be implemented in this file.
*
* The current drivers is implemented from the offical specification:
* Microsoft Extensible Firmware Initiative FAT32 File System Specification.
* The document can be found at:
* http://www.csn.ul.ie/~mel/projects/vm/
*/
/**
* Note:
* The FAT word have two significations.
*
* First, FAT is the name of the file system for File Allocation Table.
*
* Second, FAT is a structure. A cluster table at the begining of the
* partition. The FAT table maps the data region of the partition by cluster
* number.
*
* It will be specify explicitly if the understanding can be ambigus.
*/
#include "fs_fat.h"
/* ********************************************************
* FAT structures definitions {{{
*/
/** FAT Type */
#define FAT12 12
#define FAT16 16
#define FAT32 32
/**
* Converts a FAT type to a string
*/
static const char *
sos_fat_type_str (unsigned int type)
{
switch (type)
{
case FAT12:
return "FAT12";
case FAT16:
return "FAT16";
case FAT32:
return "FAT32";
default:
return "Unknown";
}
}
/** FAT cluster limit
*
* The maximum number of a FAT type.
* These constant are helpful to determine the FAT type.
*
* section "FAT Type Determination" in the offical spec.
*/
#define FAT12_CLUSTER_LIMIT 4085
#define FAT16_CLUSTER_LIMIT 65525
/** FAT EOC (End Of Cluster) marker
*
* In this context the FAT refers to the cluster table.
* When the cluster contains a end of file then the EOC marker is set in the
* mapping of this cluster in the FAT.
*/
#define FAT16_EOC 0xFFF8
#define FAT32_EOC 0x0FFFFFF8
/** FAT cluster mask
*
* In this context the FAT refers to the cluster table.
* This mask is used for the cluster number in the FAT. The mask prevent
* number overflow. We can see that the FAT16 number is a 16 bits unsigned integer
* and the FAT32 number is a 28 bits unsigned integer. The 4 higher
* significant bits are reserved.
*/
#define FAT16_CLUSTER_MASK 0xFFFF
#define FAT32_CLUSTER_MASK 0x0FFFFFFF
/** FAT cluster entry size
*
* In this context the FAT refers to the cluster table.
* The size is in bytes.
*/
#define FAT16_CLUSTER_ENTRY_SIZE 2
#define FAT32_CLUSTER_ENTRY_SIZE 4
/** FAT cluster value for the first valid cluster
*
* The first valid data cluster have number 2.
* It's specify in the section "FAT Data Structure" in the official spec.
*/
#define FAT_FIRST_VALID_CLUSTER 2
/** FAT cluster entry value for a free cluster
*
* In this context the FAT refers to the cluster table.
* This marker specify that the cluster entry (and the cluster) is free and
* the cluster is available for use.
*/
#define FAT_FREE_CLUSTER_ENTRY_VALUE 0
/** The FAT16 specific BPB
*
* BPB is the BIOS Prameter Block. The BPB is located in the first sector of
* the partition also named Boot Sector. sos_fat16_BPB and sos_fat32_BPB are
* specific parts of the FAT types, FAT16 and FAT32, and are include in the
* sos_fat_boot_sector structure above. The Boot Sector is the
* entry point of the FAT file system. It gives informations on the file
* system.
*
* FAT FS sturcture:
* __________________________________________
* |BS/BPB|FAT struct| data clusters | <--- a FAT partition.
* ------------------------------------------
*/
struct sos_fat16_BPB {
sos_ui8_t BS_DrvNum; /* Int 0x13 driver number. */
/* Under real mode operating systems such as MS-DOS, calling INT 0x13
* would jump into the computer's BIOS code for Low Level Disk Services,
* which will carry out sector-based disk read or write for the program. */
sos_ui8_t BS_Reserved1; /* Reserved (used by Windows NT). */
sos_ui8_t BS_BootSig; /* Extended boot signature (0x29). */
sos_ui32_t BS_VolID; /* Volume serial number. */
char BS_VolLab[11]; /* Volume label. */
char BS_FilSysType[8]; /* Always set to the string "FATxx " */
} __attribute__((packed));
/** The FAT32 specific BPB */
struct sos_fat32_BPB {
sos_ui32_t BPB_FATSz32; /* This field is the FAT32 32-bit count of sectors occupied by ONE FAT */
sos_ui16_t BPB_ExtFlags; /* Flags */
sos_ui16_t BPB_FSVer; /* High byte is major revision number. Low byte is minor revision number. */
sos_ui32_t BPB_RootClus; /* This is set to the cluster number of the first cluster of the root directory. */
sos_ui16_t BPB_FSInfo; /* Sector number of FSINFO structure in the reserved area of the FAT32 volume. */
sos_ui16_t BPB_BkBootSec; /* If non-zero, indicates the sector number in the reserved area of */
/* the volume of a copy of the boot record. */
char BPB_Reserved[12]; /* Reserved for future expansion. */
struct sos_fat16_BPB fat16_BPB;
} __attribute__ ((packed));
/** The FAT first sector structure */
struct sos_fat_boot_sector {
/* BS (Boot Sector) */
sos_ui8_t BS_jmpbBoot[3]; /* jmpBoot[0] = 0xEB, jmpBoot[1] = 0x??, jmpBoot[2] = 0x90 */
/* or jmpBoot[0] = 0xE9, jmpBoot[1] = 0x??, jmpBoot[2] = 0x?? */
char BS_OEMName[8]; /* MSWIN4.1 */
/* BPB (BIOS Parameter Block) */
sos_ui16_t BPB_BytsPerSec; /* Count of bytes per sector: 512, 1024, 2048 or 4096 */
sos_ui8_t BPB_SecPerClus; /* Number of sectors per allocation unit: 1, 2, 4, 8, 16, 32, 64 or 128 */
sos_ui16_t BPB_RsvdSecCnt; /* Number of reserved sectors in the Reserved region of the volume */
/* starting at the first sector of the volume. */
sos_ui8_t BPB_NumFATs; /* The count of FAT data structures on the volume. */
sos_ui16_t BPB_RootEntCnt; /* For FAT32 volumes, this field must be set to 0. */
sos_ui16_t BPB_TotSec16; /* For FAT32 volumes, this field must be 0. */
sos_ui8_t BPB_Media; /* Media type: 0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE or 0xFF */
sos_ui16_t BPB_FATSz16; /* On FAT32 volumes this field must be 0. */
sos_ui16_t BPB_SecPerTrk; /* Sectors per track for interrupt 0x13. */
sos_ui16_t BPB_NumHeads; /* Number of heads for interrupt 0x13. */
sos_ui32_t BPB_HiddSec; /* Count of hidden sectors preceding the partition that contains this FAT volume. */
sos_ui32_t BPB_TotSec32; /* This field is the new 32-bit total count of sectors on the volume. */
/* BPB specific */
union {
struct sos_fat16_BPB fat16_BPB;
struct sos_fat32_BPB fat32_BPB;
} BPB_specific;
} __attribute__ ((packed));
/** Fat Directory entry structure
*
* There are only 2 nodes types in FAT: file and directory.
* A FAT directory is nothing but a "file" composed of a linear list of
* 32-byte directory entry structures.
*/
struct sos_fat_directory_entry {
#define IS_FAT_VALID_CHAR(c) ((c) > 0x20 && (c) != 0x22 && ((c) < 0x2A || (c) > 0x2C) && /*(c) != 0x2E &&*/ (c) != 0x2F && ((c) < 0x3A || (c) > 0x3F) && ((c) < 0x5B || (c) > 0x5D) && (c) != 0x7C)
char DIR_Name[11]; /* Short name */
/* File attributes */
#define ATTR_READ_ONLY 0x01
#define ATTR_HIDDEN 0x02
#define ATTR_SYSTEM 0x04
#define ATTR_VOLUME_ID 0x08
#define ATTR_DIRECTORY 0x10
#define ATTR_ARCHIVE 0x20
#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)
sos_ui8_t DIR_Attr; /* File attributes */
sos_ui8_t DIR_NTRes; /* Reserved for use by Windows NT */
sos_ui8_t DIR_CrtTimeTenth; /* Millisecond stamp at file creation time */
sos_ui16_t DIR_CrtTime; /* Time file was created. */
sos_ui16_t DIR_CrtDate; /* Date file was created. */
sos_ui16_t DIR_LstAccDate; /* Last access date */
sos_ui16_t DIR_FstClusHI; /* High word of this entry's first cluster number. */
sos_ui16_t DIR_WrtTime; /* Time of last write. */
sos_ui16_t DIR_WrtDate; /* Date of last write. */
sos_ui16_t DIR_FstClusLO; /* Low word of this entry's first cluster number. */
sos_ui32_t DIR_FileSize; /* 32-bit DWORD holding this file's size in bytes. */
} __attribute__ ((packed));
/** Fat Info strcuture
*
* This structure is FAT32 specific. It's currently unused.
*
* This structure contains generals informations on the FAT file system. The
* main information is the current state of the FAT cluster table.
*/
struct sos_fat_info {
sos_ui32_t FSI_LeadSig; /* Value 0x41615252 */
char FSI_Reserved1[480]; /* This field is currently reserved for future expansion. */
sos_ui32_t FSI_StrucSig; /* Value 0x61417272 */
sos_ui32_t FSI_Free_Count; /* Contains the last known free cluster count on the volume */
sos_ui32_t FSI_Nxt_Free; /* It indicates the cluster number at which the driver
should start looking for free clusters */
char FSI_Reserved2[12]; /* This field is currently reserved for future expansion. */
sos_ui32_t FSI_TrailSig; /* Value 0xAA550000 */
} __attribute__ ((packed));
/** All structures before are given by the official spec.
* ======================================================
* Now starts internals SOS structures.
*/
/** Fat data structure
*
* This structure is filled during the mount operation. These fields are
* computed from BS/BPB like describe in the official specification. The
* structure is fundamental for partition use in many VFS operations.
*/
struct sos_fat_data_structure {
sos_ui32_t RootDirSectors; /* Number of sectors in root directory */
sos_ui32_t FATSz; /* FAT size */
sos_ui32_t FirstDataSector; /* First data sector */
sos_ui32_t TotSec; /* Total number of sector in the partition */
sos_ui32_t DataSec; /* Number of data sector in the partition */
sos_ui32_t CountOfClusters; /* Number of cluster in the partition */
sos_ui32_t FirstRootDirSecNum; /* First sector of the root directory */
sos_ui32_t FAT_Type; /* FAT type */
sos_ui32_t EndOfCluster; /* End of cluster marker */
struct sos_fat_boot_sector BootSector;
};
/** Fat mmap page list structure */
struct sos_fat_mmap_page_list {
struct sos_fat_mmap_page_list *next; /* Next page of the file */
sos_luoffset_t offset; /* Offset in the file. Must be a multiple of PAGE_SIZE */
sos_vaddr_t mmap_page; /* The page fill of datas of the file */
};
/** Fat file informations */
struct sos_fat_file {
struct sos_fat_directory_entry dir_entry;
/* Needed variables to map the file */
struct sos_umem_vmm_mapped_resource mapres;
sos_count_t num_mappings;
struct sos_fat_mmap_page_list *list; /* The list is ordered by offset */
};
/** The description of the "FAT FS" */
static struct sos_fs_manager_type fat_type;
/* }}} */
/* ********************************************************
* Helper functions {{{
*/
/** Helper macro to get the first cluster of the FS node (subdir / file) as
* referenced by a directory entry. */
#define GET_FIRST_CLUSTER(dir_entry) \
(((dir_entry)->DIR_FstClusHI<<16) | (dir_entry)->DIR_FstClusLO)
/** Helper function to compute the first sector number
* of the given cluster. */
static sos_ui64_t
sos_fat_helper_first_sector_of_cluster(
struct sos_fat_data_structure *data_struct,
sos_ui32_t cluster_nr)
{
return ((cluster_nr - FAT_FIRST_VALID_CLUSTER) * data_struct->BootSector.BPB_SecPerClus) + data_struct->FirstDataSector;
}
/** Helper function to compute the cluster containing
* the given sector. */
static sos_ui32_t
sos_fat_helper_cluster_of_sector(
struct sos_fat_data_structure *data_struct,
sos_ui64_t sector_nr)
{
return ((sector_nr - data_struct->FirstDataSector) / data_struct->BootSector.BPB_SecPerClus) + FAT_FIRST_VALID_CLUSTER;
}
/** Helper function to compute the FAT offset from the FAT type.
*
* In this context the FAT refers to the cluster table.
* This function compute an offset (in Bytes) in the first FAT cluster table.
*/
static sos_ui32_t sos_fat_helper_fat_offset(
struct sos_fat_data_structure *data_struct,
sos_ui32_t cluster_nr)
{
if (data_struct->FAT_Type == FAT16) {
return cluster_nr * FAT16_CLUSTER_ENTRY_SIZE;
} else if (data_struct->FAT_Type == FAT32) {
return cluster_nr * FAT32_CLUSTER_ENTRY_SIZE;
}
/** FAT12 is unsupported */
return 0;
}
/** Helper function to compute the FAT sector number in the cluster table and its offset
* in this sector for a given cluster. */
static sos_ret_t
sos_fat_helper_get_sector_and_offset(
struct sos_fat_data_structure* data_struct,
sos_ui32_t cluster_nr,
sos_ui32_t *ThisFATSecNum,
sos_ui32_t *ThisFATEntOffset)
{
*ThisFATSecNum = data_struct->BootSector.BPB_RsvdSecCnt +
(sos_fat_helper_fat_offset(data_struct, cluster_nr) / data_struct->BootSector.BPB_BytsPerSec);
*ThisFATEntOffset =
sos_fat_helper_fat_offset(data_struct, cluster_nr) % data_struct->BootSector.BPB_BytsPerSec;
return SOS_OK;
}
/** Helper function to find the first free cluster in
* the cluster table. */
static sos_ret_t
sos_fat_helper_find_free_cluster(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t *cluster_nr_result)
{
sos_ui32_t cluster_nr;
sos_ret_t retval;
sos_ui32_t ThisFATSecNum, ThisFATEntOffset;
sos_ui32_t FATClusEntryVal;
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0);
if (SecBuf == NULL) {
return -SOS_ENOMEM;
}
for (cluster_nr = FAT_FIRST_VALID_CLUSTER;
cluster_nr < data_struct->CountOfClusters;
cluster_nr++) {
sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset);
if (cluster_nr == FAT_FIRST_VALID_CLUSTER || ThisFATEntOffset == 0) {
/* In case of we are at beginning of a new sector in the FAT cluster table,
* we need this sector then we read it in SecBuf variable */
retval = sos_blockdev_kernel_read(block_device,
ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
}
/* SecBuf variable contains a sector of the FAT cluster table
* then we can get the cluster values */
if (data_struct->FAT_Type == FAT16) {
FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]);
} else {
FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK;
}
if (FATClusEntryVal == FAT_FREE_CLUSTER_ENTRY_VALUE) {
sos_kfree((sos_vaddr_t) SecBuf);
*cluster_nr_result = cluster_nr;
return SOS_OK;
}
}
/* No cluster available */
return -SOS_EIO;
}
/** Helper function to set a cluster entry in the FAT cluster table with value.
*
* In this context the FAT refers to the cluster table.
* Updates on disk the cluster entry value in the FAT cluster table with the
* value in parameter. The cluster number is the cluster_nr label in parameter.
*/
static sos_ret_t
sos_fat_helper_set_cluster_value(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t cluster_nr,
sos_ui32_t value)
{
sos_ret_t retval;
sos_ui32_t fat_nr;
sos_ui32_t ThisFATSecNum, ThisFATEntOffset;
SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER);
sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset);
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0);
if (SecBuf == NULL)
return -SOS_ENOMEM;
/* Write value on each entries of each FATs */
for (fat_nr = 0;
fat_nr < data_struct->BootSector.BPB_NumFATs;
fat_nr++) {
/* Read the FAT cluster table sector who contains the value to change.
* The sector of the FAT is in SecBuf variable */
retval = sos_blockdev_kernel_read(block_device,
((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
/* Change the cluster entry value in the SecBuf variable read below */
if (data_struct->FAT_Type == FAT16) {
*((sos_ui16_t *) &SecBuf[ThisFATEntOffset]) = (sos_ui16_t) (FAT16_CLUSTER_MASK & value);
} else {
*((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) =
(*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & (~FAT32_CLUSTER_MASK);
*((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) =
(*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) | (FAT32_CLUSTER_MASK & value);
}
/* Write on disk the modified sector of the FAT */
retval = sos_blockdev_kernel_write(block_device,
((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
return -SOS_EIO;
}
}
sos_kfree((sos_vaddr_t) SecBuf);
return SOS_OK;
}
/** Helper function to find the next cluster in the cluster list of a node
*
* In this context the FAT refers to the cluster table.
* This function read the next data cluster in the FAT cluster table.
* It reads the FAT sector of the current cluster (cluster_nr in parameter) and
* takes the cluster value of cluster_nr to get the next cluster of the
* file/dir. Result is return by next_cluster parameter.
*/
static sos_ret_t
sos_fat_helper_find_next_cluster(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t cluster_nr,
sos_ui32_t *next_cluster)
{
sos_ret_t retval;
sos_ui32_t ThisFATSecNum, ThisFATEntOffset;
sos_ui32_t FATClusEntryVal;
SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER);
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0);
if (SecBuf == NULL)
return -SOS_ENOMEM;
sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset);
/* Read the FAT sector who contains cluster_nr entry */
retval = sos_blockdev_kernel_read(block_device,
ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
/* Take the cluster_nr cluster value */
if (data_struct->FAT_Type == FAT16) {
FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]);
} else {
FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK;
}
SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE);
sos_kfree((sos_vaddr_t) SecBuf);
*next_cluster = FATClusEntryVal;
return SOS_OK;
}
/** Helper function to find the last cluster in the cluster list of a node
*
* In this context the FAT refers to the cluster table.
* This function read cluster by cluster in the FAT cluster table.
* It reads the FAT sector of the current cluster (cluster_nr in parameter) and
* loops on the cluster value of cluster_nr to get the next cluster of the
* file/dir until the end of cluster. Result is return by last_cluster parameter.
*
* This function is indirecly used in these VFS operations:
* truncate, node_descructor, new_mapping, write.
*/
static sos_ret_t
sos_fat_helper_find_last_cluster(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t cluster_nr,
sos_ui32_t *last_cluster)
{
sos_ret_t retval;
sos_ui32_t ThisFATSecNum, ThisFATEntOffset;
sos_ui32_t FATClusEntryVal = cluster_nr;
SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER);
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0);
if (SecBuf == NULL)
return -SOS_ENOMEM;
/* Loop on the cluster list. Start of the list: cluster_nr. End of the loop:
* cluster value == End Of Cluster */
do {
cluster_nr = FATClusEntryVal;
sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset);
/* Read the sector of the FAT that contains cluster_nr entry */
retval = sos_blockdev_kernel_read(block_device,
ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
/* Get the cluster value */
if (data_struct->FAT_Type == FAT16) {
FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]);
} else {
FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK;
}
SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE);
} while (FATClusEntryVal != data_struct->EndOfCluster);
sos_kfree((sos_vaddr_t) SecBuf);
*last_cluster = cluster_nr;
return SOS_OK;
}
/* Helper function to remove the last cluster of the cluster list of a node.
*
* This function is indirecly used in these VFS operations:
* truncate, node_destructor.
*/
static sos_ret_t
sos_fat_helper_remove_last_cluster(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t cluster_nr)
{
sos_ret_t retval;
sos_ui32_t fat_nr;
sos_ui32_t ThisFATSecNum, ThisFATEntOffset;
sos_ui32_t FATClusEntryVal = cluster_nr;
sos_ui32_t previous_cluster;
SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER);
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0);
if (SecBuf == NULL)
return -SOS_ENOMEM;
/* Iterate on the cluster list. Start of the list: cluster_nr. End of the
* loop: cluster value == End of cluster */
do {
previous_cluster = cluster_nr;
cluster_nr = FATClusEntryVal;
sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset);
retval = sos_blockdev_kernel_read(block_device,
ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
if (data_struct->FAT_Type == FAT16) {
FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]);
} else {
FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK;
}
SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE);
} while (FATClusEntryVal != data_struct->EndOfCluster);
/* Node is composed by more than one cluster? */
if (previous_cluster != cluster_nr) {
/* Mark the last cluster as free */
if (data_struct->FAT_Type == FAT16) {
*((sos_ui16_t *) &SecBuf[ThisFATEntOffset]) = FAT_FREE_CLUSTER_ENTRY_VALUE;
} else {
*((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) =
(*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & (~FAT32_CLUSTER_MASK);
*((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) =
(*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) | FAT_FREE_CLUSTER_ENTRY_VALUE;
}
/* Write the modified FAT sector in all FATs */
for (fat_nr = 0;
fat_nr < data_struct->BootSector.BPB_NumFATs;
fat_nr++) {
retval = sos_blockdev_kernel_write(block_device,
((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
}
/* Now mark the previous cluster as the end of the cluster list */
sos_fat_helper_get_sector_and_offset(data_struct, previous_cluster, &ThisFATSecNum, &ThisFATEntOffset);
retval = sos_blockdev_kernel_read(block_device,
ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
/* Mark the cluster as EOC */
if (data_struct->FAT_Type == FAT16) {
*((sos_ui16_t *) &SecBuf[ThisFATEntOffset]) = FAT16_EOC;
} else {
*((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) =
(*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & (~FAT32_CLUSTER_MASK);
*((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) =
(*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) | FAT32_EOC;
}
/* Write the modified FAT sector in all FATs */
for (fat_nr = 0;
fat_nr < data_struct->BootSector.BPB_NumFATs;
fat_nr++) {
retval = sos_blockdev_kernel_write(block_device,
((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
}
}
sos_kfree((sos_vaddr_t) SecBuf);
return SOS_OK;
}
/** Helper function to remove all clusters of a node
*
* This function is indirecly used in these VFS operations:
* node_destructor.
*/
static sos_ret_t
sos_fat_helper_remove_all_cluster(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t first_cluster)
{
sos_ret_t retval;
sos_ui32_t current_cluster, next_cluster;
SOS_ASSERT_FATAL(first_cluster >= FAT_FIRST_VALID_CLUSTER);
current_cluster = first_cluster;
while (current_cluster != data_struct->EndOfCluster) {
retval = sos_fat_helper_find_next_cluster(block_device,
data_struct,
current_cluster,
&next_cluster);
if (retval != SOS_OK) {
return retval;
}
retval = sos_fat_helper_set_cluster_value(block_device,
data_struct,
current_cluster,
FAT_FREE_CLUSTER_ENTRY_VALUE);
if (retval != SOS_OK) {
return retval;
}
current_cluster = next_cluster;
}
retval = sos_fat_helper_set_cluster_value(block_device,
data_struct,
current_cluster,
FAT_FREE_CLUSTER_ENTRY_VALUE);
if (retval != SOS_OK) {
return retval;
}
return SOS_OK;
}
/** Helper function to append a new cluster at the end of
* the cluster list of a node.
*
* This function is indirecly used in these VFS operations:
* truncate, new_mapping, write.
*/
static sos_ret_t
sos_fat_helper_add_new_cluster(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t first_cluster,
sos_ui32_t *new_cluster)
{
sos_ret_t retval;
sos_ui32_t last_cluster;
SOS_ASSERT_FATAL(first_cluster >= FAT_FIRST_VALID_CLUSTER);
retval = sos_fat_helper_find_last_cluster(
block_device,
data_struct,
first_cluster,
&last_cluster);
if (retval != SOS_OK)
return retval;
retval = sos_fat_helper_find_free_cluster(
block_device,
data_struct,
new_cluster);
if (retval != SOS_OK)
return retval;
sos_fat_helper_set_cluster_value(
block_device,
data_struct,
last_cluster,
*new_cluster);
if (retval != SOS_OK)
return retval;
sos_fat_helper_set_cluster_value(
block_device,
data_struct,
*new_cluster,
data_struct->EndOfCluster);
if (retval != SOS_OK)
return retval;
return SOS_OK;
}
/** Helper function read a slice of node.
*
* The slice to read starts at 'start_storage_location' offset of the
* partition and has a length of 'len' bytes.
*
* This function is used in these VFS functions:
* read, link and mmap.
*/
static sos_ret_t
sos_fat_helper_read(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui64_t start_storage_location,
sos_vaddr_t dest_buf,
sos_size_t * /* in/out */len)
{
sos_ret_t retval;
sos_ui32_t next_cluster;
sos_size_t clus_size = data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus;
sos_ui32_t clus_len = (*len / clus_size) + 1;
int i;
sos_size_t current_len, saved_len, total_len = 0;
/* Read the file by cluster */
for (i = 0; i < clus_len; i++) {
/* The first and the last clusters are the only clusters whose can be not full */
if (i == 0) {
/* In the first cluster the begining can be truncate */
current_len = clus_size - (start_storage_location % clus_size);
if (current_len > *len)
current_len = *len;
} else if (i == clus_len - 1) {
/* In the last cluster the end can be truncate */
current_len = *len - total_len;
if (current_len == 0)
break;
} else {
current_len = clus_size;
}
saved_len = current_len;
retval = sos_blockdev_kernel_read(block_device,
start_storage_location,
dest_buf + total_len,
&current_len);
if (retval != SOS_OK) {
return retval;
} else if (current_len != saved_len) {
return -SOS_EIO;
}
total_len += current_len;
if (total_len == *len)
break;
retval = sos_fat_helper_find_next_cluster(
block_device,
data_struct,
sos_fat_helper_cluster_of_sector(data_struct,
start_storage_location / data_struct->BootSector.BPB_BytsPerSec),
&next_cluster);
if (retval != SOS_OK) {
return retval;
}
start_storage_location = sos_fat_helper_first_sector_of_cluster(data_struct, next_cluster)
* data_struct->BootSector.BPB_BytsPerSec;
}
return SOS_OK;
}
/** Helper function to find a free entry in a directory node.
*
* This function iterates on the clusters list of a directory to find a free
* directory entry. A directory is simply a sequencial list of 32-Bytes directory entry.
* A free directory can be found when DIR_Name[0] == 0 or 0xe5.
*/
static sos_ret_t
sos_fat_helper_find_free_directory_entry(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui64_t start_storage_location,
sos_ui64_t *new_storage_location)
{
sos_ret_t retval;
int i, j;
sos_ui32_t cluster_nr, sector_limit;
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_size_t dirent_size = sizeof(struct sos_fat_directory_entry);
struct sos_fat_directory_entry *dir_entry = (struct sos_fat_directory_entry *) sos_kmalloc(dirent_size, 0);
sos_ui64_t sector_nr = start_storage_location / sector_size;
sos_ui64_t sector_rem = start_storage_location % sector_size;
if (sector_nr >= data_struct->FirstRootDirSecNum &&
sector_nr < data_struct->FirstDataSector) {
sector_limit = data_struct->RootDirSectors;
} else {
sector_limit = data_struct->BootSector.BPB_SecPerClus;
}
/* Loop on directory's clusters */
do {
/* Loop on the cluster's sectors */
for (i = 0; i < sector_limit; i++) {
/* Loop on the sector's directory entries */
for (j = sector_rem / sizeof(struct sos_fat_directory_entry);
j < sector_size / sizeof(struct sos_fat_directory_entry);
j++) {
retval = sos_fat_helper_read(block_device,
data_struct,
((sector_nr + i) * sector_size) +
(j * sizeof(struct sos_fat_directory_entry)),
(sos_vaddr_t)dir_entry,
&dirent_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
/* A directory entry is free if DIR_Name[0] == 0xe5 or 0 */
if ((dir_entry->DIR_Name[0] & 0xFF) == 0xE5 ||
(dir_entry->DIR_Name[0] & 0xFF) == 0) {
*new_storage_location = ((sector_nr + i) * sector_size) +
(j * sizeof(struct sos_fat_directory_entry));
sos_kfree((sos_vaddr_t) dir_entry);
return SOS_OK;
}
}
}
/* Find the next cluster of the directory */
if (sector_nr >= data_struct->FirstRootDirSecNum &&
sector_nr < data_struct->FirstDataSector) {
/* Root directory is a special case */
break;
} else {
cluster_nr = sos_fat_helper_cluster_of_sector(data_struct, sector_nr);
retval = sos_fat_helper_find_next_cluster(block_device, data_struct, cluster_nr, &cluster_nr);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
}
} while (cluster_nr != data_struct->EndOfCluster);
sos_kfree((sos_vaddr_t) dir_entry);
/* No free entry in this directory */
return -SOS_ENOENT;
}
/** Helper function to write an entry in the current directory. */
static sos_ret_t
sos_fat_helper_set_directory_entry(
struct sos_blockdev_instance *block_device,
sos_ui64_t storage_location,
struct sos_fat_directory_entry *dir_entry)
{
sos_ret_t retval;
sos_size_t dir_entry_size = sizeof(struct sos_fat_directory_entry);
retval = sos_blockdev_kernel_write(block_device,
storage_location,
(sos_vaddr_t) dir_entry,
&dir_entry_size);
if (retval != SOS_OK) {
return retval;
} else if (dir_entry_size != sizeof(struct sos_fat_directory_entry)) {
return -SOS_EIO;
}
return SOS_OK;
}
/** Helper function to copy from an entry in a string.
*
* This function will convert the FAT specific name format to the generic VFS
* format. readdir operation use it.
*/
static sos_ret_t
sos_fat_helper_copy_file_name(
struct sos_fat_directory_entry *dir_entry,
char *name,
sos_ui16_t *namelen)
{
int i = 0, j;
char c;
if (dir_entry->DIR_Name[i] == 0x05) {
name[i] = 0xE5;
i++;
}
for ( ; i < 8; i++) {
c = dir_entry->DIR_Name[i];
if (c != ' ') {
if (IS_FAT_VALID_CHAR(c)) {
name[i] = dir_entry->DIR_Name[i];
} else {
return -SOS_EINVAL;
}
} else {
break;
}
}
if (dir_entry->DIR_Name[8] != ' ') {
name[i++] = '.';
} else {
*namelen = i;
return SOS_OK;
}
for (j = 8; j < 11; j++) {
c = dir_entry->DIR_Name[j];
if (c != ' ') {
if (IS_FAT_VALID_CHAR(c)) {
name[i++] = dir_entry->DIR_Name[j];
} else {
return -SOS_EINVAL;
}
} else {
break;
}
}
*namelen = i;
return SOS_OK;
}
static sos_bool_t
fat_nsnode_same_name(const char *name1, sos_ui16_t namelen1,
const char *name2, sos_ui16_t namelen2);
/** Helper function to compare names between a FAT's entry and a string. */
static sos_bool_t
sos_fat_helper_same_name(const char *name, sos_ui16_t namelen, struct sos_fat_directory_entry *dir_entry)
{
char name2[SOS_FS_DIRENT_NAME_MAXLEN];
memset(name2, 0, SOS_FS_DIRENT_NAME_MAXLEN);
sos_ui16_t namelen2 = SOS_FS_DIRENT_NAME_MAXLEN;
sos_fat_helper_copy_file_name(dir_entry, name2, &namelen2);
return fat_nsnode_same_name(name, namelen, name2, namelen2);
}
/** Hepler function to get the next entry in a directory.
*
* This function skip empty directory entry to return the first not free
* directory entry. A directory entry shouldn't have a DIR_Name[0] == 0xe5 or
* 0 and shouldn't be a long name entry (DIR_Attr == ATTR_LONG_NAME).
*/
static sos_ret_t
sos_fat_helper_get_next_directory_entry(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui64_t storage_location,
struct sos_fat_directory_entry **dir_entry,
sos_ui64_t *dir_storage_location,
sos_si64_t *dir_offset)
{
sos_ret_t retval;
int i, j;
sos_ui32_t cluster_nr, sector_limit;
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
struct sos_fat_directory_entry *SecBuf = (struct sos_fat_directory_entry *) sos_kmalloc(sector_size, 0);
sos_ui64_t sector_nr = storage_location / data_struct->BootSector.BPB_BytsPerSec;
sos_ui32_t sector_rem = (storage_location % data_struct->BootSector.BPB_BytsPerSec) /
sizeof(struct sos_fat_directory_entry);
*dir_offset = 0;
if (sector_nr >= data_struct->FirstRootDirSecNum &&
sector_nr < data_struct->FirstDataSector) {
sector_limit = data_struct->FirstRootDirSecNum + data_struct->RootDirSectors;
} else {
sector_limit =
sos_fat_helper_first_sector_of_cluster(
data_struct,
sos_fat_helper_cluster_of_sector(
data_struct,
sector_nr)) +
data_struct->BootSector.BPB_SecPerClus;
}
/* loop on directory's clusters */
do {
/* loop on cluster's sectors */
for (i = sector_nr; i < sector_limit; i++) {
retval = sos_blockdev_kernel_read(block_device,
i * sector_size,
(sos_vaddr_t) SecBuf,
&sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
/* loop on directory's entry in sector i */
for (j = sector_rem; j < sector_size / sizeof(struct sos_fat_directory_entry); j++) {
*dir_offset += sizeof(struct sos_fat_directory_entry);
/* A directory entry is normal if DIR_Name[0] != 0xe5 and 0
* and is not a long name directory entry */
if ((SecBuf[j].DIR_Name[0] & 0xFF) != 0xE5 &&
(SecBuf[j].DIR_Name[0] & 0xFF) != 0 &&
SecBuf[j].DIR_Attr != ATTR_LONG_NAME) {
if (dir_entry != NULL) {
memcpy(*dir_entry, &SecBuf[j], sizeof(struct sos_fat_directory_entry));
}
*dir_storage_location = (i * sector_size) +
(j * sizeof(struct sos_fat_directory_entry));
sos_kfree((sos_vaddr_t) SecBuf);
return SOS_OK;
} else if ( (SecBuf[j].DIR_Name[0] & 0xFF) == 0 ) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_ENOENT;
}
}
}
/* Find the next cluster of the directory */
if (sector_nr >= data_struct->FirstRootDirSecNum &&
sector_nr < data_struct->FirstDataSector) {
/* Root directory is a special case */
break;
} else {
cluster_nr = sos_fat_helper_cluster_of_sector(data_struct, sector_nr);
retval = sos_fat_helper_find_next_cluster(block_device, data_struct, cluster_nr, &cluster_nr);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
}
}
sector_rem = 0;
} while (cluster_nr != data_struct->EndOfCluster);
sos_kfree((sos_vaddr_t) SecBuf);
/* No next entry in the directory */
return -SOS_ENOENT;
}
/** Helper function to compute the storage location in a file
* from an offset in this file.
*
* This function, with the cluster number find in the directory entry and an
* offset (both in parameters), computes the storage location of the byte from
* the beginning of the partition with the FAT cluster list.
* The position is a cluster and an offset in this cluster. When cluster is
* known, the FAT cluster list can be loop on until this cluster is find then
* the cluster is convert in the absolut position in the partition. This
* position is added with the offset in this cluster and the storage location
* is find.
*/
static sos_ret_t
sos_fat_helper_storage_location_of_file(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
sos_ui32_t first_cluster_of_file,
sos_ui32_t offset_in_file,
sos_ui64_t *storage_location)
{
sos_ret_t retval;
sos_ui32_t ThisFATSecNum, ThisFATEntOffset;
sos_ui32_t FATClusEntryVal = first_cluster_of_file;
sos_ui32_t cluster_nr;
int i;
SOS_ASSERT_FATAL(first_cluster_of_file >= FAT_FIRST_VALID_CLUSTER);
sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec;
sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0);
if (SecBuf == NULL)
return -SOS_ENOMEM;
/* Compute the cluster of the position and the offset in this cluster of the
* position */
sos_ui32_t cluster_offset = offset_in_file / (sector_size * data_struct->BootSector.BPB_SecPerClus);
sos_ui32_t offset_rem = offset_in_file % (sector_size * data_struct->BootSector.BPB_SecPerClus);
/* Find the cluster computed below */
for (i = 0; i < cluster_offset; i++) {
cluster_nr = FATClusEntryVal;
sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset);
retval = sos_blockdev_kernel_read(block_device,
ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)SecBuf, &sector_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) SecBuf);
return retval;
} else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
if (data_struct->FAT_Type == FAT16) {
FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]);
} else {
FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK;
}
SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE);
if (FATClusEntryVal == data_struct->EndOfCluster) {
sos_kfree((sos_vaddr_t) SecBuf);
return -SOS_EIO;
}
}
/* Adds the offset in the cluster to find the storage location */
*storage_location =
(sos_fat_helper_first_sector_of_cluster(data_struct, FATClusEntryVal) * sector_size) + offset_rem;
sos_kfree((sos_vaddr_t) SecBuf);
return SOS_OK;
}
/** Helper function to resize a file.
*
* Only file can be resize.
* With the new_size parameter, clusters to add or to delete can be compute
* and modified as desired.
*
* This function is used in these VFS operations:
* truncate, new_mapping, write.
*/
static sos_ret_t
sos_fat_helper_resize(
struct sos_blockdev_instance *block_device,
struct sos_fat_data_structure *data_struct,
struct sos_fs_node *fs_node,
sos_ui32_t new_size)
{
int i;
sos_ret_t retval;
struct sos_fat_directory_entry *dir_entry = fs_node->custom_data;
sos_ui32_t first_cluster;
sos_ui32_t old_size;
if (dir_entry != NULL) {
first_cluster = GET_FIRST_CLUSTER(dir_entry);
old_size = dir_entry->DIR_FileSize;
} else {
first_cluster = data_struct->FirstRootDirSecNum;
old_size = data_struct->RootDirSectors;
}
sos_size_t clus_size =
(data_struct->BootSector.BPB_SecPerClus * data_struct->BootSector.BPB_BytsPerSec);
/* How many clusters before and after the resize? */
sos_ui32_t new_cluster, old_cluster, free_cluster;
new_cluster = new_size / clus_size;
old_cluster = old_size / clus_size;
/* File should be bigger or smaller? */
if (new_cluster > old_cluster) {
/* Bigger */
sos_ui32_t *clus_buf = (sos_ui32_t*)sos_kmalloc(clus_size, 0);
if (clus_buf == NULL)
return -SOS_ENOMEM;
memset(clus_buf, 0, clus_size);
for (i = 0; i < new_cluster - old_cluster; i++) {
retval = sos_fat_helper_add_new_cluster(block_device,
data_struct,
first_cluster,
&free_cluster);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) clus_buf);
return retval;
}
/* Clean the new cluster */
retval = sos_blockdev_kernel_write(block_device,
sos_fat_helper_first_sector_of_cluster(data_struct, free_cluster) *
data_struct->BootSector.BPB_BytsPerSec,
(sos_vaddr_t)clus_buf,
&clus_size);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) clus_buf);
return retval;
} else if (clus_size != data_struct->BootSector.BPB_SecPerClus * data_struct->BootSector.BPB_BytsPerSec) {
sos_kfree((sos_vaddr_t) clus_buf);
return -SOS_EIO;
}
}
sos_kfree((sos_vaddr_t) clus_buf);
} else if (old_cluster > new_cluster) {
/* Smaller */
for (i = 0; i < old_cluster - new_cluster; i++) {
retval = sos_fat_helper_remove_last_cluster(block_device,
data_struct,
first_cluster);
if (retval != SOS_OK)
return retval;
if (dir_entry != NULL)
dir_entry->DIR_FileSize -= clus_size;
}
}
if (dir_entry != NULL) {
dir_entry->DIR_FileSize = new_size;
dir_entry->DIR_Attr |= ATTR_ARCHIVE;
}
return SOS_OK;
}
/* }}} */
/* ********************************************************
* File mapping stuff {{{
*/
inline static struct sos_fat_file *
get_fatnode_of_vr(struct sos_umem_vmm_vr * vr)
{
struct sos_umem_vmm_mapped_resource *mr
= sos_umem_vmm_get_mapped_resource_of_vr(vr);
return (struct sos_fat_file *)(((struct sos_fs_node *)mr->custom_data)->custom_data);
}
static void fat_map_ref(struct sos_umem_vmm_vr * vr)
{
struct sos_fat_file * fatnode = get_fatnode_of_vr(vr);
sos_fs_ref_fsnode((struct sos_fs_node *) fatnode->mapres.custom_data);
fatnode->num_mappings++;
}
static void fat_map_unref(struct sos_umem_vmm_vr * vr)
{
struct sos_fat_file * fatnode = get_fatnode_of_vr(vr);
SOS_ASSERT_FATAL(fatnode->num_mappings > 0);
fatnode->num_mappings --;
_sos_fs_unref_fsnode((struct sos_fs_node *) fatnode->mapres.custom_data);
}
static void fat_map_unmap(struct sos_umem_vmm_vr * vr,
sos_uaddr_t uaddr,
sos_size_t size)
{
sos_paging_unmap_interval(uaddr, size);
}
static sos_ret_t fat_map_page_in(struct sos_umem_vmm_vr * vr,
sos_uaddr_t uaddr,
sos_bool_t write_access)
{
struct sos_fat_file * fatnode = get_fatnode_of_vr(vr);
sos_luoffset_t offset = uaddr - sos_umem_vmm_get_start_of_vr(vr);
sos_ret_t retval;
sos_paddr_t ppage_paddr;
sos_vaddr_t page_vaddr;
sos_bool_t allocate_new_page = FALSE;
struct sos_fat_mmap_page_list *elt, *previous_elt = NULL;
/* The region is not allowed to be resized */
if (SOS_PAGE_ALIGN_SUP(offset) > fatnode->dir_entry.DIR_FileSize)
return -SOS_EFAULT;
if (fatnode->list != NULL) {
/* There are already mapping on this file */
elt = fatnode->list;
while (elt != NULL) {
if (elt->offset == sos_umem_vmm_get_offset_in_resource(vr)) {
/* page already allocated and filled */
page_vaddr = elt->mmap_page;
break;
} else if (elt->offset > sos_umem_vmm_get_offset_in_resource(vr)) {
/* Allocate a kernel page to fill */
page_vaddr = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP);
if (page_vaddr == (sos_vaddr_t)NULL)
return -SOS_ENOMEM;
allocate_new_page = TRUE;
break;
}
previous_elt = elt;
elt = elt->next;
}
} else {
/* Allocate a kernel page to fill */
page_vaddr = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP);
if (page_vaddr == (sos_vaddr_t)NULL)
return -SOS_ENOMEM;
allocate_new_page = TRUE;
}
struct sos_fs_node *fs_node = (struct sos_fs_node *)
sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data;
struct sos_fat_data_structure *data_struct =
(struct sos_fat_data_structure *)fs_node->fs->custom_data;
/* Fill the mapping in the new allocated page with the data read from the file */
if (allocate_new_page) {
sos_size_t page_size = SOS_PAGE_SIZE;
sos_ui64_t storage_location;
offset = SOS_PAGE_ALIGN_INF(offset);
retval = sos_fat_helper_storage_location_of_file(fs_node->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(&fatnode->dir_entry),
offset + sos_umem_vmm_get_offset_in_resource(vr),
&storage_location);
if (retval != SOS_OK) {
sos_kmem_vmm_free(page_vaddr);
return retval;
}
retval = sos_fat_helper_read(fs_node->fs->device->block_device,
data_struct,
storage_location,
page_vaddr,
&page_size);
if (retval != SOS_OK) {
sos_kmem_vmm_free(page_vaddr);
return retval;
}
}
/* Lookup physical kernel page */
ppage_paddr = sos_paging_get_paddr(page_vaddr);
/* Cannot access unmapped kernel pages */
if (! ppage_paddr) {
sos_kmem_vmm_free(page_vaddr);
return -SOS_EFAULT;
}
/* Initialize a mmap page element and insert this element in the file mmap list */
if (allocate_new_page) {
elt = (struct sos_fat_mmap_page_list *) sos_kmalloc(sizeof(struct sos_fat_mmap_page_list), 0);
if (elt == NULL) {
sos_kmem_vmm_free(page_vaddr);
return -SOS_ENOMEM;
}
elt->mmap_page = page_vaddr;
elt->offset = sos_umem_vmm_get_offset_in_resource(vr);
if (fatnode->list != NULL) {
/* There is one or more element in the list */
if (previous_elt == NULL) {
/* There is no element before elt */
elt->next = fatnode->list;
fatnode->list = elt;
} else {
/* elt must be insert after previous_elt */
elt->next = previous_elt->next;
previous_elt->next = elt;
}
} else {
/* elt is the first of the list */
elt->next = NULL;
fatnode->list = elt;
}
}
/* Remap it in user space */
return sos_paging_map(ppage_paddr,
SOS_PAGE_ALIGN_INF(uaddr),
TRUE,
sos_umem_vmm_get_prot_of_vr(vr));
}
static sos_ret_t fat_map_sync_page(struct sos_umem_vmm_vr * vr,
sos_uaddr_t uaddr,
sos_ui32_t flags)
{
struct sos_fat_file * fatnode = get_fatnode_of_vr(vr);
sos_luoffset_t offset = SOS_PAGE_ALIGN_INF(uaddr - sos_umem_vmm_get_start_of_vr(vr));
sos_ret_t retval;
int i;
sos_size_t clus_size, clus_size_write;
sos_ui64_t storage_location;
struct sos_fs_node *fs_node = (struct sos_fs_node *)
sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data;
struct sos_fat_data_structure *data_struct =
(struct sos_fat_data_structure *)fs_node->fs->custom_data;
clus_size = clus_size_write = data_struct->BootSector.BPB_SecPerClus *
data_struct->BootSector.BPB_BytsPerSec;
/* Sync all cluster in a memory page */
for (i = 0; i < SOS_PAGE_SIZE / clus_size; i++) {
/* Get the storage location of the cluster */
retval = sos_fat_helper_storage_location_of_file(fs_node->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(&fatnode->dir_entry),
offset + sos_umem_vmm_get_offset_in_resource(vr) + (i * clus_size),
&storage_location);
if (retval != SOS_OK) {
return retval;
}
/* Sync the cluster */
sos_blockdev_kernel_write(fs_node->fs->device->block_device,
storage_location,
uaddr + (i * clus_size),
&clus_size_write);
if (retval != SOS_OK) {
return retval;
} else if (clus_size_write != clus_size) {
return -SOS_EIO;
}
}
/* Unset the dirty flag */
sos_paging_set_dirty(uaddr, FALSE);
return SOS_OK;
}
static struct sos_umem_vmm_vr_ops fat_map_ops
= (struct sos_umem_vmm_vr_ops){
.ref = fat_map_ref,
.unref = fat_map_unref,
.unmap = fat_map_unmap,
.page_in = fat_map_page_in,
.sync_page = fat_map_sync_page
};
static sos_ret_t fat_new_mapping(struct sos_umem_vmm_vr *vr)
{
struct sos_fat_file * fat_file = get_fatnode_of_vr(vr);
struct sos_fs_node *fs_node = fat_file->mapres.custom_data;
sos_size_t reqsize;
sos_ret_t retval;
reqsize = sos_umem_vmm_get_offset_in_resource(vr);
reqsize += sos_umem_vmm_get_size_of_vr(vr);
/* Resize the region NOW when the mapping size is greater than the file size */
if (reqsize > fat_file->dir_entry.DIR_FileSize)
{
retval = sos_fat_helper_resize(fs_node->fs->device->block_device,
fs_node->fs->custom_data,
fs_node,
SOS_PAGE_ALIGN_SUP(reqsize));
if (SOS_OK != retval)
return retval;
}
return sos_umem_vmm_set_ops_of_vr(vr, &fat_map_ops);
}
/* }}} */
/* ********************************************************
* Opened file operations {{{
*/
static sos_ret_t
fat_duplicate_opened_file(struct sos_fs_opened_file *this,
const struct sos_process * for_owner,
struct sos_fs_opened_file **result)
{
*result = (struct sos_fs_opened_file*)
sos_kmalloc(sizeof(struct sos_fs_opened_file), 0);
if (! *result)
return -SOS_ENOMEM;
/* Copy the parent node */
memcpy(*result, this, sizeof(*this));
/* Change the owner */
(*result)->owner = for_owner;
/* If a custom_data exist, copy it too */
if (this->custom_data != NULL) {
struct sos_fs_dirent *dirent = (struct sos_fs_dirent*)
sos_kmalloc(sizeof(struct sos_fs_dirent), 0);
if (dirent == NULL)
return -SOS_ENOMEM;
/* Copy and set the custom_data of the parent */
memcpy(dirent, this->custom_data, sizeof(*dirent));
(*result)->custom_data = dirent;
}
return SOS_OK;
}
static sos_ret_t
fat_seek(struct sos_fs_opened_file *this,
sos_lsoffset_t offset,
sos_seek_whence_t whence,
/* out */ sos_lsoffset_t * result_position)
{
sos_lsoffset_t ref_offs;
struct sos_fs_node * fatnode;
fatnode = (struct sos_fs_node*)
sos_fs_nscache_get_fs_node(this->direntry);
if (fatnode->type != SOS_FS_NODE_REGULAR_FILE)
return -SOS_ENOSUP;
*result_position = this->position;
switch (whence)
{
case SOS_SEEK_SET:
ref_offs = 0;
break;
case SOS_SEEK_CUR:
ref_offs = this->position;
break;
case SOS_SEEK_END:
ref_offs = ((struct sos_fat_directory_entry*) fatnode->custom_data)->DIR_FileSize;
break;
default:
return -SOS_EINVAL;
}
/* Prevent the underflow of the file position */
if (offset < -ref_offs)
return -SOS_EINVAL;
/* Update the file position */
this->position = ref_offs + offset;
*result_position = this->position;
return SOS_OK;
}
static sos_ret_t fat_read(struct sos_fs_opened_file *this,
sos_uaddr_t dest_buf,
sos_size_t * /* in/out */len)
{
sos_ret_t retval;
struct sos_fs_node * fatnode;
struct sos_fat_directory_entry *dir_entry;
struct sos_fat_data_structure *data_struct;
sos_ui64_t storage_location;
sos_vaddr_t kern_buf;
fatnode = (struct sos_fs_node*)
sos_fs_nscache_get_fs_node(this->direntry);
data_struct = (struct sos_fat_data_structure*) fatnode->fs->custom_data;
dir_entry = fatnode->custom_data;
if (fatnode->type != SOS_FS_NODE_REGULAR_FILE)
return -SOS_ENOSUP;
/* Cannot read after end of file */
if (this->position >= dir_entry->DIR_FileSize)
{
*len = 0;
return SOS_OK;
}
/* Cannot read more than the file size */
if (this->position + *len >= dir_entry->DIR_FileSize)
*len = dir_entry->DIR_FileSize - this->position;
retval = sos_fat_helper_storage_location_of_file(
fatnode->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(dir_entry),
this->position,
&storage_location);
if (retval != SOS_OK) {
return retval;
}
kern_buf = sos_kmalloc(*len, 0);
if (kern_buf == (sos_vaddr_t)NULL)
return -SOS_ENOMEM;
retval = sos_fat_helper_read(fatnode->fs->device->block_device,
data_struct,
storage_location,
kern_buf,
len);
if (retval != SOS_OK) {
sos_kfree(kern_buf);
return retval;
}
retval = sos_memcpy_to_user(dest_buf,
(sos_vaddr_t)kern_buf,
*len);
if (retval < 0) {
sos_kfree((sos_vaddr_t)kern_buf);
return retval;
}
this->position += retval;
*len = retval;
sos_kfree((sos_vaddr_t)kern_buf);
return SOS_OK;
}
static sos_ret_t fat_write(struct sos_fs_opened_file *this,
sos_uaddr_t src_buf,
sos_size_t * /* in/out */len)
{
sos_ret_t retval;
struct sos_fs_node * fatnode;
struct sos_fat_directory_entry *dir_entry;
struct sos_fat_data_structure *data_struct;
sos_ui64_t storage_location;
sos_vaddr_t kern_buf;
fatnode = (struct sos_fs_node*)
sos_fs_nscache_get_fs_node(this->direntry);
dir_entry = fatnode->custom_data;
data_struct = fatnode->fs->custom_data;
if (fatnode->type != SOS_FS_NODE_REGULAR_FILE)
return -SOS_ENOSUP;
kern_buf = sos_kmalloc(*len, 0);
if (kern_buf == (sos_vaddr_t)NULL)
return -SOS_ENOMEM;
if (this->position + *len >= dir_entry->DIR_FileSize)
{
/* Try to resize if needed */
if ( SOS_OK != sos_fat_helper_resize(
fatnode->fs->device->block_device,
data_struct,
fatnode,
this->position + *len) ) {
*len = dir_entry->DIR_FileSize - this->position;
} else {
sos_fs_mark_dirty(this);
}
}
retval = sos_memcpy_from_user(kern_buf,
src_buf,
*len);
if (retval < 0)
{
sos_kfree(kern_buf);
return retval;
}
sos_size_t clus_size = data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus;
sos_ui32_t clus_pos = this->position / clus_size;
sos_ui32_t clus_len = (this->position + *len) / clus_size;
int i;
sos_size_t current_len, saved_len, total_len = 0;
sos_ui32_t current_pos = this->position;
/* Write buffer by cluster */
for (i = 0; i < clus_len - clus_pos + 1; i++) {
/* First and last clusters can be not full clusters */
if (i == 0) {
/* The first cluster begining can be truncate */
current_len = clus_size - (this->position % clus_size);
if (current_len > *len)
current_len = *len;
} else if (i == clus_len - clus_pos) {
/* The last cluster end can be truncate */
current_len = *len - total_len;
if (current_len == 0)
break;
} else {
current_len = clus_size;
}
saved_len = current_len;
retval = sos_fat_helper_storage_location_of_file(
fatnode->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(dir_entry),
current_pos,
&storage_location);
if (retval != SOS_OK) {
sos_kfree(kern_buf);
return retval;
}
retval = sos_blockdev_kernel_write(fatnode->fs->device->block_device,
storage_location,
kern_buf + (total_len/sizeof(sos_vaddr_t)),
&current_len);
if (retval != SOS_OK) {
sos_kfree(kern_buf);
return retval;
} else if (current_len != saved_len) {
sos_kfree(kern_buf);
return -SOS_EIO;
}
current_pos += current_len;
total_len += current_len;
}
dir_entry->DIR_Attr |= ATTR_ARCHIVE;
this->position = current_pos;
*len = total_len;
/* Set the dirty flag */
sos_fs_mark_dirty(this);
sos_kfree(kern_buf);
return SOS_OK;
}
static sos_ret_t fat_mmap(struct sos_fs_opened_file *this,
sos_uaddr_t *uaddr, sos_size_t size,
sos_ui32_t access_rights,
sos_ui32_t flags,
sos_luoffset_t offset)
{
struct sos_fs_node * fat_node;
struct sos_fat_file * fat_file;
fat_node = (struct sos_fs_node*)
sos_fs_nscache_get_fs_node(this->direntry);
if (fat_node->type != SOS_FS_NODE_REGULAR_FILE)
return -SOS_ENOSUP;
fat_file = (struct sos_fat_file *) fat_node->custom_data;
/* Set the dirty flag if file can be writen */
if (access_rights & SOS_VM_MAP_PROT_WRITE)
sos_fs_mark_dirty(this);
return sos_umem_vmm_map(sos_process_get_address_space(this->owner),
uaddr, size, access_rights,
flags, &fat_file->mapres, offset);
}
static sos_ret_t fat_readdir(struct sos_fs_opened_file *this,
struct sos_fs_dirent * result)
{
/* For directories, "custom_data" indicates the address
of the last directory entry */
sos_ret_t retval;
struct sos_fat_directory_entry *dir_entry, *this_dir_entry;
struct sos_fs_node * fatnode;
struct sos_fat_data_structure *data_struct;
struct sos_fs_dirent *fs_dirent;
sos_ui64_t storage_location;
sos_si64_t offset;
fatnode = (struct sos_fs_node*)
sos_fs_nscache_get_fs_node(this->direntry);
SOS_ASSERT_FATAL(fatnode->type == SOS_FS_NODE_DIRECTORY);
data_struct = (struct sos_fat_data_structure *) fatnode->fs->custom_data;
this_dir_entry = (struct sos_fat_directory_entry *) fatnode->custom_data;
dir_entry = (struct sos_fat_directory_entry*) sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0);
fs_dirent = (struct sos_fs_dirent *) this->custom_data;
/* First acces on the directory entries ? */
if (fs_dirent == NULL) {
fs_dirent = (struct sos_fs_dirent *) sos_kmalloc(sizeof(struct sos_fs_dirent), 0);
memset(fs_dirent, 0, sizeof(struct sos_fs_dirent));
if ( this_dir_entry == NULL ) {
storage_location = data_struct->FirstRootDirSecNum *
data_struct->BootSector.BPB_BytsPerSec;
} else {
storage_location = sos_fat_helper_first_sector_of_cluster(
data_struct,
GET_FIRST_CLUSTER(this_dir_entry)) *
data_struct->BootSector.BPB_BytsPerSec;
}
} else {
storage_location = fs_dirent->storage_location + sizeof(struct sos_fat_directory_entry);
}
retval = sos_fat_helper_get_next_directory_entry(
fatnode->fs->device->block_device,
data_struct,
storage_location,
&dir_entry,
&storage_location,
&offset);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
if (this->custom_data == NULL) {
sos_kfree((sos_vaddr_t) fs_dirent);
}
return retval;
}
/* Update the result */
memset(result, 0, sizeof(struct sos_fs_dirent));
result->storage_location = storage_location;
result->offset_in_dirfile = this->position + offset;
if (dir_entry->DIR_Attr & ATTR_DIRECTORY) {
result->type = SOS_FS_NODE_DIRECTORY;
} else {
result->type = SOS_FS_NODE_REGULAR_FILE;
}
retval = sos_fat_helper_copy_file_name(dir_entry, result->name, &result->namelen);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
memcpy(fs_dirent, result, sizeof(struct sos_fs_dirent));
/* Update the custom data */
this->position += offset;
this->custom_data = (void *) fs_dirent;
sos_kfree((sos_vaddr_t) dir_entry);
return SOS_OK;
}
static struct sos_fs_ops_opened_file fat_ops_opened_file =
(struct sos_fs_ops_opened_file){
.seek = fat_seek,
.read = fat_read,
.write = fat_write,
.mmap = fat_mmap,
/*.fcntl = fat_fcntl*/
};
static struct sos_fs_ops_opened_dir fat_ops_opened_dir =
(struct sos_fs_ops_opened_dir){
.readdir = fat_readdir
};
/* }}} */
/* ********************************************************
* FS node operations {{{
*/
static sos_ret_t fat_stat_node(struct sos_fs_node * this,
struct sos_fs_stat * result)
{
/* Establish the major/minor fields */
result->st_rdev.device_class = 0;
result->st_rdev.device_instance = 0;
/* In FAT, nodes are only file or directory, there is no special file.
Retrieve it from the device that it is mounted
on (might not exist...) */
struct sos_fs_node * rootdev = this->fs->device;
if (rootdev)
{
result->st_rdev.device_class = rootdev->dev_id.device_class;
result->st_rdev.device_instance = rootdev->dev_id.device_instance;
}
result->st_type = this->type;
result->st_access_rights = this->access_rights;
result->st_nlink = this->ondisk_lnk_cnt;
/* Separate root directory and other nodes */
if (this->custom_data != NULL) {
result->st_size = (sos_si64_t)((struct sos_fat_directory_entry *) this->custom_data)->DIR_FileSize;
result->st_storage_location = this->storage_location;
} else {
struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure *) this->fs->custom_data;
result->st_size = 0;
result->st_storage_location = (sos_ui64_t) data_struct->FirstRootDirSecNum * data_struct->BootSector.BPB_BytsPerSec;
}
return SOS_OK;
}
static sos_ret_t fat_truncate(struct sos_fs_node *this,
sos_lsoffset_t length)
{
sos_ret_t retval;
struct sos_fat_directory_entry *dir_entry;
struct sos_fat_data_structure *data_struct;
sos_size_t new_size;
if ( (this->type != SOS_FS_NODE_REGULAR_FILE) )
return -SOS_ENOSUP;
new_size = (sos_size_t) length;
dir_entry = (struct sos_fat_directory_entry*) this->custom_data;
data_struct = (struct sos_fat_data_structure*) this->fs->custom_data;
sos_ui32_t current_clus = dir_entry->DIR_FileSize /
(data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus);
sos_ui32_t cluster = new_size /
(data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus);
int i;
sos_ui32_t last_cluster;
if (dir_entry->DIR_FileSize >= new_size) {
for (i = 0; i < current_clus - cluster; i++) {
retval = sos_fat_helper_remove_last_cluster(
this->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(dir_entry));
if (retval != SOS_OK)
return retval;
}
} else {
for (i = 0; i < cluster - current_clus; i++) {
retval = sos_fat_helper_add_new_cluster(
this->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(dir_entry),
&last_cluster);
if (retval != SOS_OK)
return retval;
}
}
dir_entry->DIR_FileSize = new_size;
dir_entry->DIR_Attr |= ATTR_ARCHIVE;
return SOS_OK;
}
/**
* This function syncronize the in-memory data on the disk.
* Datas to write on disk are directory entry of the node and, for files, mappings.
*/
static sos_ret_t fat_sync_node(struct sos_fs_node *this)
{
sos_ret_t retval = SOS_OK;
sos_size_t clus_size, clus_size_write;
sos_ui64_t storage_location;
if (this->custom_data != NULL && this->storage_location != 0) {
/* Sync file's mappings */
if (this->type == SOS_FS_NODE_REGULAR_FILE) {
struct sos_fat_file *fat_file = (struct sos_fat_file *)this->custom_data;
/* Mapping list is empty? */
if (fat_file->list != NULL) {
struct sos_fat_mmap_page_list *elt = fat_file->list;
struct sos_fat_data_structure *data_struct =
(struct sos_fat_data_structure *)this->fs->custom_data;
clus_size = clus_size_write = data_struct->BootSector.BPB_SecPerClus *
data_struct->BootSector.BPB_BytsPerSec;
/* Loop on the mappings */
while (elt != NULL) {
unsigned int i;
/* Loop on the pages of the current mapping */
for (i = 0; i < SOS_PAGE_SIZE / clus_size; i++) {
retval = sos_fat_helper_storage_location_of_file(this->fs->device->block_device,
data_struct,
GET_FIRST_CLUSTER(&fat_file->dir_entry),
elt->offset + (i * clus_size),
&storage_location);
if (retval != SOS_OK) {
return retval;
}
/* Write on disk the page */
sos_blockdev_kernel_write(this->fs->device->block_device,
storage_location,
elt->mmap_page + (i * clus_size),
&clus_size_write);
if (retval != SOS_OK) {
return retval;
} else if (clus_size_write != clus_size) {
return -SOS_EIO;
}
}
elt = elt->next;
}
}
}
/* Sync the directory entry of the node */
retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device,
this->storage_location,
(struct sos_fat_directory_entry *) this->custom_data);
}
return retval;
}
static sos_ret_t fat_chmod_node(struct sos_fs_node * this,
sos_ui32_t access_rights)
{
struct sos_fat_directory_entry * dir_entry =
(struct sos_fat_directory_entry*)this->custom_data;
if (dir_entry != NULL) {
if ( (access_rights & SOS_FS_READABLE) &&
!(access_rights & SOS_FS_WRITABLE) ) {
/* Read-Only case */
if (! (dir_entry->DIR_Attr & ATTR_READ_ONLY)) {
dir_entry->DIR_Attr |= ATTR_READ_ONLY;
dir_entry->DIR_Attr |= ATTR_ARCHIVE;
}
} else {
/* Read-Write case */
if ( (access_rights & SOS_FS_READABLE) &&
(dir_entry->DIR_Attr & ATTR_READ_ONLY) ) {
dir_entry->DIR_Attr &= ~ATTR_READ_ONLY;
dir_entry->DIR_Attr |= ATTR_ARCHIVE;
}
}
}
this->access_rights = access_rights;
return SOS_OK;
}
static struct sos_fs_node_ops_file fat_ops_file =
(struct sos_fs_node_ops_file){
.truncate = fat_truncate,
.stat = fat_stat_node,
.chmod = fat_chmod_node,
.sync = fat_sync_node
};
/** Callback when nothing (in particular no sos_fs_nscache_node) make
reference to the node */
static sos_ret_t fat_node_destructor(struct sos_fs_node * this)
{
sos_ret_t retval;
struct sos_fat_directory_entry * dir_entry =
(struct sos_fat_directory_entry*)this->custom_data;
if (dir_entry != NULL) {
/* This callback is called only when the fsnode is not needed
anymore by any process or by the kernel. But the node must remain
stored "on disk" as long as the FS is mounted AND the node is
still "on disk" */
if (this->ondisk_lnk_cnt <= 0 && this->storage_location != 0)
{
/* Disable the directory entry of the node */
dir_entry->DIR_Name[0] = 0xE5;
/* Free allocated clusters of the file */
retval = sos_fat_helper_remove_all_cluster(this->fs->device->block_device,
this->fs->custom_data,
GET_FIRST_CLUSTER(dir_entry));
if (retval != SOS_OK) {
return retval;
}
/* Write modified directory entry */
retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device,
this->storage_location,
dir_entry);
if (retval != SOS_OK) {
return retval;
}
}
/* Free file's mappings elements */
if (this->type == SOS_FS_NODE_REGULAR_FILE) {
struct sos_fat_mmap_page_list *elt_tmp, *elt = ((struct sos_fat_file *) this->custom_data)->list;
while (elt != NULL) {
sos_kmem_vmm_free(elt->mmap_page);
elt_tmp = elt->next;
sos_kfree((sos_vaddr_t)elt);
elt = elt_tmp;
}
}
sos_kfree((sos_vaddr_t) dir_entry);
}
sos_kfree((sos_vaddr_t) this);
return SOS_OK;
}
static sos_ret_t fat_new_opened_file(struct sos_fs_node * this,
const struct sos_process * owner,
sos_ui32_t open_flags,
struct sos_fs_opened_file ** result_of)
{
struct sos_fs_opened_file * of
= (struct sos_fs_opened_file*)sos_kmalloc(sizeof(struct sos_fs_opened_file), 0);
if (! of)
return -SOS_ENOMEM;
memset(of, 0x0, sizeof(*of));
of->owner = owner;
of->duplicate = fat_duplicate_opened_file;
of->open_flags = open_flags;
of->ops_file = &fat_ops_opened_file;
if (this->type == SOS_FS_NODE_DIRECTORY)
of->ops_dir = & fat_ops_opened_dir;
*result_of = of;
return SOS_OK;
}
static sos_ret_t fat_close_opened_file(struct sos_fs_node * this,
struct sos_fs_opened_file * of)
{
if (of->custom_data != NULL)
sos_kfree((sos_vaddr_t)of->custom_data);
sos_kfree((sos_vaddr_t)of);
return SOS_OK;
}
static sos_ret_t fat_dir_lookup(struct sos_fs_node *this,
const char * name, sos_ui16_t namelen,
sos_ui64_t * result_storage_location)
{
sos_ret_t retval;
sos_ui64_t sector_nr, storage_location;
sos_si64_t offset;
struct sos_fat_directory_entry *this_dir_entry, *dir_entry;
struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure *) this->fs->custom_data;
this_dir_entry = (struct sos_fat_directory_entry*)this->custom_data;
dir_entry = (struct sos_fat_directory_entry*) sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0);
if (dir_entry == NULL)
return -SOS_ENOMEM;
if (this_dir_entry != NULL) {
sector_nr = sos_fat_helper_first_sector_of_cluster(data_struct,
GET_FIRST_CLUSTER(this_dir_entry));
} else {
sector_nr = data_struct->FirstRootDirSecNum;
}
storage_location = sector_nr * data_struct->BootSector.BPB_BytsPerSec;
/* Loop on the directory entries */
do {
retval = sos_fat_helper_get_next_directory_entry(this->fs->device->block_device,
data_struct,
storage_location,
&dir_entry,
&storage_location,
&offset);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t)dir_entry);
return retval;
}
/* Is it the good node entry? */
if (sos_fat_helper_same_name(name, namelen, dir_entry) == TRUE)
{
*result_storage_location = storage_location;
sos_kfree((sos_vaddr_t) dir_entry);
return SOS_OK;
}
storage_location += sizeof(struct sos_fat_directory_entry);
} while (retval != -SOS_ENOENT);
/* No entry match */
sos_kfree((sos_vaddr_t) dir_entry);
return -SOS_ENOENT;
}
/** fat_link is the specific FAT VFS hard link function.
*
* The node 'node' in parameter will be link with the parent node 'this'. A
* link occurs when a hard link on 'node' is needed.
* FAT filesystem doesn't allow much than one hard link then link can only be
* execute when a file or a directory is created.
*
* This function is used in these VFS operations:
* rename, link, open (with the O_CREAT flag), creat, mkdir, symlink, mknod.
*
* FAT doesn't allow symlink, mknod operation.
* rename operation doesn't work too because of the SOS implementation of this
* operation. In SOS, rename begins by made a hard link on the old file to
* rename and then unlink the old file. Makes a hard link on the old file is
* forbidden.
*
* Others operations are allowed. This function makes many things:
* - Check is a hard link is asked. In this case returns an error.
* - Copy the file name in the directory entry structure.
* - Allocate the first cluster of the file and clean it.
* - Mark the corresponding FAT cluster entry as EOC.
* - Create the directory entries '.' and '..' if the node is a directory.
* - Set the new directory entry in its parent directory 'this'.
* - Insert the new node in the nodes' hashtable.
* - Increment the link counter of the node and its parent.
*
*/
static sos_ret_t fat_link(struct sos_fs_node *this,
const struct sos_process *actor,
const char * entry_name, sos_ui16_t entry_namelen,
struct sos_fs_node * node)
{
sos_ret_t retval;
sos_ui32_t new_cluster;
struct sos_fat_directory_entry * dir_entry;
struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure*)this->fs->custom_data;
/* Hard link not allowed in FAT */
if (node->storage_location != 0) {
if (node->ondisk_lnk_cnt > 0) {
if (islower(entry_name[0])) {
((struct sos_fat_directory_entry*)node->custom_data)->DIR_Name[0] = entry_name[0] - 0x20;
} else {
((struct sos_fat_directory_entry*)node->custom_data)->DIR_Name[0] = entry_name[0];
}
retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device,
node->storage_location,
(struct sos_fat_directory_entry *) node->custom_data);
if (retval != SOS_OK) {
return retval;
}
}
if (node->ondisk_lnk_cnt == 0) {
node->ondisk_lnk_cnt++;
this->ondisk_lnk_cnt++;
}
return -SOS_ENOSUP;
}
if (node->type == SOS_FS_NODE_REGULAR_FILE) {
dir_entry = &((struct sos_fat_file *)node->custom_data)->dir_entry;
} else {
dir_entry = (struct sos_fat_directory_entry*)sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0);
if (! dir_entry)
return -SOS_ENOMEM;
}
memset(dir_entry, 0, sizeof(struct sos_fat_directory_entry));
/* Copy the node name in the directory entry structure*/
int i = 0, j, k;
if (entry_namelen > 12) {
sos_kfree((sos_vaddr_t) dir_entry);
return -SOS_ENAMETOOLONG;
}
/* Case of the Kanji 0xE5 in the first character */
if ((entry_name[i] & 0xFF) == 0xE5) {
dir_entry->DIR_Name[i] = 0x05;
i++;
}
/* Copy the name part of the node name */
for (; i < 8; i++) {
if (entry_name[i] == '.' || i >= entry_namelen) {
break;
}
if (IS_FAT_VALID_CHAR(entry_name[i])) {
if (islower(entry_name[i])) {
dir_entry->DIR_Name[i] = entry_name[i] - 0x20;
} else {
dir_entry->DIR_Name[i] = entry_name[i];
}
} else {
sos_kfree((sos_vaddr_t) dir_entry);
return -SOS_EINVAL;
}
}
if (i < entry_namelen && entry_name[i] != '.') {
sos_kfree((sos_vaddr_t) dir_entry);
return -SOS_ENAMETOOLONG;
}
/* Fill rest by space ' ' */
for (k = i; k < 8; k++) {
dir_entry->DIR_Name[k] = ' ';
}
i++;
/* Copy the extension part of the node name */
for (j = 0; j < 3; j++) {
if (i >= entry_namelen)
break;
if (IS_FAT_VALID_CHAR(entry_name[i])) {
if (islower(entry_name[i])) {
dir_entry->DIR_Name[j+8] = entry_name[i] - 0x20;
} else {
dir_entry->DIR_Name[j+8] = entry_name[i];
}
i++;
} else {
sos_kfree((sos_vaddr_t) dir_entry);
return -SOS_EINVAL;
}
}
if (i < entry_namelen && j == 2) {
sos_kfree((sos_vaddr_t) dir_entry);
return -SOS_ENAMETOOLONG;
}
/* Fill rest by space ' ' */
for (k = j; k < 3; k++) {
dir_entry->DIR_Name[k+8] = ' ';
}
/* Find the first cluster of the node */
retval = sos_fat_helper_find_free_cluster(this->fs->device->block_device, data_struct, &new_cluster);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
/* Set EOC marker in the corresponding FAT cluster entry */
retval = sos_fat_helper_set_cluster_value(
this->fs->device->block_device,
data_struct,
new_cluster,
data_struct->EndOfCluster);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
dir_entry->DIR_FstClusHI = (new_cluster>>16) & 0xFFFF;
dir_entry->DIR_FstClusLO = new_cluster & 0xFFFF;
sos_size_t cluster_size = (data_struct->BootSector.BPB_BytsPerSec *
data_struct->BootSector.BPB_SecPerClus);
sos_ui64_t new_cluster_storage_location =
sos_fat_helper_first_sector_of_cluster(data_struct, new_cluster) *
data_struct->BootSector.BPB_BytsPerSec;
/* Clean the new cluster */
sos_vaddr_t zero_buf = sos_kmalloc(cluster_size, 0);
if (((void *)zero_buf) == NULL) {
sos_kfree((sos_vaddr_t)dir_entry);
return -SOS_ENOMEM;
}
memset((void *)zero_buf, 0, cluster_size);
retval = sos_blockdev_kernel_write(this->fs->device->block_device,
new_cluster_storage_location,
zero_buf,
&cluster_size);
sos_kfree((sos_vaddr_t)zero_buf);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t)dir_entry);
return retval;
} else if (cluster_size !=
(data_struct->BootSector.BPB_BytsPerSec *
data_struct->BootSector.BPB_SecPerClus)) {
sos_kfree((sos_vaddr_t)dir_entry);
return -SOS_EIO;
}
dir_entry->DIR_Attr = ATTR_ARCHIVE;
if (! (node->access_rights & SOS_FS_WRITABLE) ) {
dir_entry->DIR_Attr |= ATTR_READ_ONLY;
}
/* Write entry . and .. for a directory node */
if (node->type == SOS_FS_NODE_DIRECTORY) {
dir_entry->DIR_Attr |= ATTR_DIRECTORY;
struct sos_fat_directory_entry dot_entry;
memcpy(&dot_entry, dir_entry, sizeof(struct sos_fat_directory_entry));
memcpy(dot_entry.DIR_Name, ". ", 11);
dot_entry.DIR_Attr = ATTR_DIRECTORY;
retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device,
new_cluster_storage_location,
&dot_entry);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t)dir_entry);
return retval;
}
struct sos_fat_directory_entry *dotdot_entry = this->custom_data;
if (dotdot_entry != NULL) {
memcpy(&dot_entry, dotdot_entry, sizeof(struct sos_fat_directory_entry));
} else {
memset(&dot_entry, 0, sizeof(struct sos_fat_directory_entry));
}
memcpy(dot_entry.DIR_Name, ".. ", 11);
dot_entry.DIR_Attr = ATTR_DIRECTORY;
retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device,
new_cluster_storage_location + sizeof(struct sos_fat_directory_entry),
&dot_entry);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t)dir_entry);
return retval;
}
}
node->custom_data = dir_entry;
sos_ui64_t sector_nr;
if (this->custom_data != NULL) {
struct sos_fat_directory_entry * this_dir_entry = this->custom_data;
sector_nr = sos_fat_helper_first_sector_of_cluster(data_struct,
GET_FIRST_CLUSTER(this_dir_entry));
} else {
sector_nr = data_struct->FirstRootDirSecNum;
}
sos_hash_remove(node->fs->nodecache, node);
/* While a storage location is used, it can't be re-used */
struct sos_fs_node *unused_node;
node->storage_location = sector_nr * data_struct->BootSector.BPB_BytsPerSec;
do {
retval = sos_fat_helper_find_free_directory_entry(
this->fs->device->block_device,
data_struct,
node->storage_location,
&node->storage_location);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
unused_node = (struct sos_fs_node *) sos_hash_lookup(this->fs->nodecache, &node->storage_location);
node->storage_location += sizeof(struct sos_fat_directory_entry);
} while (unused_node != NULL);
node->storage_location -= sizeof(struct sos_fat_directory_entry);
/* Set the new directory entry in its parent directory */
retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, node->storage_location, dir_entry);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
/* Insert the node in the node hashtable */
sos_hash_insert(node->fs->nodecache, node);
/* Increment the link counter of the node and its parent */
node->ondisk_lnk_cnt ++;
this->ondisk_lnk_cnt ++;
return SOS_OK;
}
/**
* This function search the entry in parameter and, if it present, decrements the
* ondisk_lnk_cnt counter for the node and its parent directory.
*/
static sos_ret_t
fat_unlink(struct sos_fs_node *this,
const struct sos_process *actor,
const char * entry_name, sos_ui16_t entry_namelen)
{
sos_ret_t retval;
struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure *) this->fs->custom_data;
struct sos_fat_directory_entry *result_dir_entry, *dir_entry = (struct sos_fat_directory_entry *) this->custom_data;
result_dir_entry = (struct sos_fat_directory_entry *) sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0);
if (result_dir_entry == NULL)
return -SOS_ENOMEM;
sos_ui64_t storage_location, sector_nr;
sos_si64_t offset;
if (dir_entry != NULL) {
sos_ui32_t cluster_nr = GET_FIRST_CLUSTER(dir_entry);
sector_nr = sos_fat_helper_first_sector_of_cluster(data_struct, cluster_nr);
} else {
sector_nr = data_struct->FirstRootDirSecNum;
}
storage_location = sector_nr * data_struct->BootSector.BPB_BytsPerSec;
/* Find the node entry in the current directory */
do {
/* Read the next directory entry */
retval = sos_fat_helper_get_next_directory_entry(
this->fs->device->block_device,
data_struct,
storage_location,
&result_dir_entry,
&storage_location,
&offset);
if (retval != SOS_OK) {
sos_kfree((sos_vaddr_t) result_dir_entry);
return retval;
}
/* Compare name and directory entry */
if (sos_fat_helper_same_name(entry_name, entry_namelen, result_dir_entry) == TRUE) {
this->ondisk_lnk_cnt--;
struct sos_fs_node *node = (struct sos_fs_node *) sos_hash_lookup(this->fs->nodecache, &storage_location);
node->ondisk_lnk_cnt--;
/* The node should be destroy on the disk? */
if (node->ondisk_lnk_cnt <= 0) {
/* Free this directory entry */
result_dir_entry->DIR_Name[0] = 0xE5;
retval = sos_fat_helper_set_directory_entry(
this->fs->device->block_device,
storage_location,
result_dir_entry);
sos_kfree((sos_vaddr_t) result_dir_entry);
if (retval != SOS_OK) {
return retval;
}
}
return SOS_OK;
}
/* Next directory entry */
storage_location += sizeof(struct sos_fat_directory_entry);
} while (result_dir_entry->DIR_Name[0] != 0);
sos_kfree((sos_vaddr_t) result_dir_entry);
return -SOS_ENOENT;
}
static struct sos_fs_node_ops_dir fat_ops_dir
= (struct sos_fs_node_ops_dir){
.lookup = fat_dir_lookup,
.link = fat_link,
.unlink = fat_unlink
};
/* }}} */
/* ********************************************************
* FS instance operations {{{
*/
/**
* This function fetch the directory entry structure and "convert" it in the
* sos_fs_node generic VFS structure.
*/
static sos_ret_t
fat_fetch_node_from_disk(struct sos_fs_manager_instance * this,
sos_ui64_t storage_location,
struct sos_fs_node ** result)
{
sos_ret_t retval;
struct sos_fs_node * fat_node;
struct sos_fat_data_structure *data_struct = this->custom_data;
/* clean the result in case of error */
*result = NULL;
fat_node = (struct sos_fs_node *) sos_kmalloc(sizeof(struct sos_fs_node), 0);
if (fat_node == NULL)
return -SOS_ENOMEM;
/* Initialize node */
memset(fat_node, 0, sizeof(struct sos_fs_node));
fat_node->fs = this;
fat_node->storage_location = storage_location;
fat_node->ondisk_lnk_cnt = 1;
fat_node->ops_file = &fat_ops_file;
fat_node->destructor = fat_node_destructor;
fat_node->new_opened_file = fat_new_opened_file;
fat_node->close_opened_file = fat_close_opened_file;
sos_size_t dir_entry_size = sizeof(struct sos_fat_directory_entry);
struct sos_fat_directory_entry *dir_entry =
(struct sos_fat_directory_entry *)
sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0);
if (dir_entry == NULL) {
sos_kfree((sos_vaddr_t)fat_node);
return -SOS_ENOMEM;
}
/* Read the on disk node's informations */
retval = sos_fat_helper_read(this->device->block_device, data_struct,
storage_location, (sos_vaddr_t)dir_entry, &dir_entry_size);
if (retval != SOS_OK){
sos_kfree((sos_vaddr_t)fat_node);
sos_kfree((sos_vaddr_t)dir_entry);
return retval;
}
/* Set type and access of the node */
fat_node->custom_data = dir_entry;
if (dir_entry->DIR_Attr & ATTR_DIRECTORY) {
fat_node->type = SOS_FS_NODE_DIRECTORY;
fat_node->ops_dir = &fat_ops_dir;
} else {
fat_node->type = SOS_FS_NODE_REGULAR_FILE;
}
fat_node->access_rights = SOS_FS_READABLE | SOS_FS_EXECUTABLE;
if (! (dir_entry->DIR_Attr & ATTR_READ_ONLY) ) {
fat_node->access_rights |= SOS_FS_WRITABLE;
}
/* Initialize ondisk_lnk_cnt for directory.
* For file ondisk_lnk_cnt must be 1 because
* hard link is forbidden in FAT */
if (fat_node->type == SOS_FS_NODE_DIRECTORY)
{
fat_node->ondisk_lnk_cnt--;
sos_si64_t offset;
int i;
storage_location = sos_fat_helper_first_sector_of_cluster(data_struct,
GET_FIRST_CLUSTER(dir_entry)) *
data_struct->BootSector.BPB_BytsPerSec;
/* Skip directory '.' and '..' to the ondisk_lnk_cnt counter */
for (i = 0; i < 2; i++) {
retval = sos_fat_helper_get_next_directory_entry(this->device->block_device,
data_struct,
storage_location,
NULL,
&storage_location,
&offset);
if (retval != SOS_OK && retval != -SOS_ENOENT) {
sos_kfree((sos_vaddr_t) fat_node);
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
storage_location += sizeof(struct sos_fat_directory_entry);
}
/* Increment ondisk_lnk_cnt on each directory entry */
while (retval != -SOS_ENOENT) {
fat_node->ondisk_lnk_cnt++;
retval = sos_fat_helper_get_next_directory_entry(this->device->block_device,
data_struct,
storage_location,
NULL,
&storage_location,
&offset);
if (retval != SOS_OK && retval != -SOS_ENOENT) {
sos_kfree((sos_vaddr_t) fat_node);
sos_kfree((sos_vaddr_t) dir_entry);
return retval;
}
storage_location += sizeof(struct sos_fat_directory_entry);
}
}
/* Initialize mapping structure */
if (fat_node->type == SOS_FS_NODE_REGULAR_FILE)
{
struct sos_fat_file * fat_file = (struct sos_fat_file *) sos_kmalloc(sizeof(struct sos_fat_file), 0);
if (fat_file == NULL)
return -SOS_ENOMEM;
memcpy(&fat_file->dir_entry, dir_entry, sizeof(struct sos_fat_directory_entry));
memset(&fat_file->mapres, 0,
sizeof(struct sos_fat_file) - sizeof(struct sos_fat_directory_entry));
sos_kfree((sos_vaddr_t) dir_entry);
fat_file->mapres.allowed_access_rights
= SOS_VM_MAP_PROT_READ
| SOS_VM_MAP_PROT_WRITE
| SOS_VM_MAP_PROT_EXEC;
fat_file->mapres.custom_data = fat_node;
fat_file->mapres.mmap = fat_new_mapping;
fat_file->num_mappings = 0;
fat_node->custom_data = fat_file;
}
/* Set the sos_fs_node structure result */
*result = fat_node;
return SOS_OK;
}
/*
* This function allocate a new node for the VFS system but this node is not
* write on disk in this function. It will be write in the sync VFS operation.
*/
static sos_ret_t
fat_allocate_new_node(struct sos_fs_manager_instance * this,
sos_fs_node_type_t type,
const struct sos_process * creator,
sos_ui32_t access_rights,
sos_ui32_t flags,
struct sos_fs_node ** result)
{
struct sos_fs_node * fat_node;
/* clean the result in case of error */
*result = NULL;
/* Allow only DIRs or FILEs files */
if ((type != SOS_FS_NODE_REGULAR_FILE)
&& (type != SOS_FS_NODE_DIRECTORY))
return -SOS_ENOSUP;
fat_node = (struct sos_fs_node*) sos_kmalloc(sizeof(struct sos_fs_node), 0);
if (! fat_node)
return -SOS_ENOMEM;
memset(fat_node, 0x0, sizeof(struct sos_fs_node));
/* Initialize "file" */
fat_node->inmem_ref_cnt = 1;
fat_node->type = type;
fat_node->access_rights = access_rights;
fat_node->ops_file = &fat_ops_file;
fat_node->destructor = fat_node_destructor;
fat_node->new_opened_file = fat_new_opened_file;
fat_node->close_opened_file = fat_close_opened_file;
if (type == SOS_FS_NODE_DIRECTORY)
fat_node->ops_dir = &fat_ops_dir;
/* Initialize mapping structure */
if (type == SOS_FS_NODE_REGULAR_FILE)
{
struct sos_fat_file * fat_file = (struct sos_fat_file *) sos_kmalloc(sizeof(struct sos_fat_file), 0);
if (fat_file == NULL)
return -SOS_ENOMEM;
memset(fat_file, 0, sizeof(struct sos_fat_file));
fat_file->mapres.allowed_access_rights
= SOS_VM_MAP_PROT_READ
| SOS_VM_MAP_PROT_WRITE
| SOS_VM_MAP_PROT_EXEC;
fat_file->mapres.custom_data = fat_node;
fat_file->mapres.mmap = fat_new_mapping;
fat_file->num_mappings = 0;
fat_node->custom_data = fat_file;
}
/* Set the sos_fs_node structure result */
*result = fat_node;
return SOS_OK;
}
/*
* By default the VFS is case sensitive but in the FAT file system the case
* isn't. That's why we redefine the file node compare function of the VFS. This
* function is generic: it use only VFS name and not FAT DIR_Name field.
*/
static sos_bool_t
fat_nsnode_same_name(const char *name1, sos_ui16_t namelen1,
const char *name2, sos_ui16_t namelen2)
{
if (!name1)
SOS_ASSERT_FATAL(namelen1 == 0);
if (!name2)
SOS_ASSERT_FATAL(namelen2 == 0);
if (namelen1 != namelen2)
return FALSE;
if (namelen1 == 0)
return TRUE;
int i;
for (i = 0; i < namelen1; i++) {
if (name1[i] == name2[i]) {
continue;
} else if ( (isupper(name1[i]) || islower(name1[i]))
&& (isupper(name2[i]) || islower(name2[i])) ) {
if (isupper(name1[i])) {
if (name1[i] != name2[i] - 0x20) {
return FALSE;
}
} else {
if (name1[i] - 0x20 != name2[i]) {
return FALSE;
}
}
} else {
return FALSE;
}
}
return TRUE;
}
/*}}}*/
/* ********************************************************
* FS type (mount/umount) operations {{{
*/
static sos_ret_t fat_mount(struct sos_fs_manager_type * this,
struct sos_fs_node * device,
const char * args,
struct sos_fs_manager_instance ** mounted_fs)
{
sos_ret_t retval;
struct sos_fs_manager_instance * fs;
struct sos_fs_node * fsnode_root;
struct sos_hash_table * hash;
sos_size_t first_sector_size;
struct sos_fat_boot_sector *sos_fat_bs;
*mounted_fs = (struct sos_fs_manager_instance*)NULL;
/* Create FS node hash table */
hash = sos_hash_create("fat H", struct sos_fs_node,
sos_hash_ui64,
sos_hash_key_eq_ui64, 17,
storage_location, hlink_nodecache);
if (! hash)
return -SOS_ENOMEM;
fs = (struct sos_fs_manager_instance*)
sos_kmalloc(sizeof(struct sos_fs_manager_instance), 0);
if (! fs)
{
sos_hash_dispose(hash);
return -SOS_ENOMEM;
}
/* Initialize fs structure */
memset(fs, 0x0, sizeof(struct sos_fs_manager_instance));
fs->fs_type = this;
fs->allocate_new_node = fat_allocate_new_node;
fs->fetch_node_from_disk = fat_fetch_node_from_disk;
fs->device = device;
/*fs->statfs = fat_statfs;*/
fs->nsnode_same_name = fat_nsnode_same_name;
fs->nodecache = hash;
/* Read boot sector */
first_sector_size = sizeof(struct sos_fat_boot_sector);
struct sos_fat_data_structure *data_struct =
(struct sos_fat_data_structure*)
sos_kmalloc(sizeof(struct sos_fat_data_structure), 0);
retval = sos_blockdev_kernel_read(device->block_device,
0 /* the FAT boot sector nr */,
(sos_vaddr_t)&data_struct->BootSector,
&first_sector_size);
if ( (retval != SOS_OK) ||
(first_sector_size != sizeof(struct sos_fat_boot_sector)) )
{
sos_bochs_printf("Can't read first sector of the fat partition (only %dB read on %dB)\n",
first_sector_size,
sizeof(struct sos_fat_boot_sector));
return retval;
}
fs->custom_data = (void *)data_struct;
sos_fat_bs = &data_struct->BootSector;
/* Compute FAT fs informations */
if (sos_fat_bs->BPB_FATSz16 != 0)
data_struct->FATSz = sos_fat_bs->BPB_FATSz16;
else
data_struct->FATSz = sos_fat_bs->BPB_specific.fat32_BPB.BPB_FATSz32;
sos_bochs_printf("FAT size: %u\nNumber FATs %u\n",
(unsigned int)data_struct->FATSz,
(unsigned int)sos_fat_bs->BPB_NumFATs);
if (sos_fat_bs->BPB_BytsPerSec == 0) {
sos_bochs_printf("Bytes per sector == 0 : NOT A FAT PARTITION");
return -SOS_EIO;
}
data_struct->RootDirSectors =
SOS_ALIGN_SUP((sos_fat_bs->BPB_RootEntCnt * 32),
sos_fat_bs->BPB_BytsPerSec) /
sos_fat_bs->BPB_BytsPerSec;
sos_bochs_printf("Root Directory Sectors: %u\n", (unsigned int) data_struct->RootDirSectors);
data_struct->FirstDataSector =
sos_fat_bs->BPB_RsvdSecCnt +
(sos_fat_bs->BPB_NumFATs * data_struct->FATSz) +
data_struct->RootDirSectors;
sos_bochs_printf("First Data Sector: %u\n", (unsigned int) data_struct->FirstDataSector);
if (sos_fat_bs->BPB_TotSec16 != 0)
data_struct->TotSec = sos_fat_bs->BPB_TotSec16;
else
data_struct->TotSec = sos_fat_bs->BPB_TotSec32;
sos_bochs_printf("Total sector: %u\n", (unsigned int) data_struct->TotSec);
data_struct->DataSec = data_struct->TotSec -
(sos_fat_bs->BPB_RsvdSecCnt +
(sos_fat_bs->BPB_NumFATs * data_struct->FATSz) +
data_struct->RootDirSectors);
sos_bochs_printf("Data sector: %u\n", (unsigned int) data_struct->DataSec);
if (sos_fat_bs->BPB_SecPerClus == 0) {
sos_bochs_printf("Sector per cluster == 0 : NOT A FAT PARTITION");
return -SOS_EIO;
}
data_struct->CountOfClusters = data_struct->DataSec / sos_fat_bs->BPB_SecPerClus;
sos_bochs_printf("Count of Clusters: %u\n", (unsigned int) data_struct->CountOfClusters);
/* Store the FAT type in the data structure. */
/* In the following code, when it says <, it does not mean <=. Note also
* that the numbers are correct. The first number for FAT12 is 4085; the
* second number for FAT16 is 65525. These numbers and the '<' signs are not
* wrong.
* This is the one and only way that FAT type is determined. There is no
* such thing as a FAT12 volume that has more than 4084 clusters. There is
* no such thing as a FAT16 volume that has less than 4085 clusters or more
* than 65,524 clusters. There is no such thing as a FAT32 volume that has
* less than 65,525 clusters. */
if (data_struct->CountOfClusters < FAT12_CLUSTER_LIMIT) {
data_struct->FAT_Type = FAT12;
} else if (data_struct->CountOfClusters < FAT16_CLUSTER_LIMIT) {
data_struct->FAT_Type = FAT16;
} else {
data_struct->FAT_Type = FAT32;
}
/* Store the End Of Cluster marker of the corresponding FAT type. */
if (data_struct->FAT_Type == FAT16) {
data_struct->EndOfCluster = FAT16_EOC;
} else {
data_struct->EndOfCluster = FAT32_EOC;
}
sos_bochs_printf("Volume is %s (count of clusters = %u)\n",
sos_fat_type_str(data_struct->FAT_Type),
(unsigned int)data_struct->CountOfClusters);
if (data_struct->FAT_Type != FAT32) {
data_struct->FirstRootDirSecNum = sos_fat_bs->BPB_RsvdSecCnt + (sos_fat_bs->BPB_NumFATs * sos_fat_bs->BPB_FATSz16);
} else {
data_struct->FirstRootDirSecNum = sos_fat_bs->BPB_specific.fat32_BPB.BPB_RootClus;
}
sos_bochs_printf("First Root Directory Sector Number: %u\n",
(unsigned int) data_struct->FirstRootDirSecNum);
fsnode_root = (struct sos_fs_node *) sos_kmalloc(sizeof(struct sos_fs_node), 0);
if (fsnode_root == NULL)
{
sos_hash_dispose(hash);
sos_kfree((sos_vaddr_t) fs);
return retval;
}
/* Initialize the root directory structure */
memset(fsnode_root, 0, sizeof(struct sos_fs_node));
fsnode_root->fs = fs;
fsnode_root->storage_location = (data_struct->FirstRootDirSecNum
* data_struct->BootSector.BPB_BytsPerSec)
- sizeof(struct sos_fat_directory_entry);
fsnode_root->inmem_ref_cnt = 1;
fsnode_root->type = SOS_FS_NODE_DIRECTORY;
fsnode_root->access_rights = SOS_FS_READABLE | SOS_FS_WRITABLE | SOS_FS_EXECUTABLE;
fsnode_root->dev_id.device_class = device->dev_id.device_class;
fsnode_root->dev_id.device_instance = device->dev_id.device_instance;
fsnode_root->ops_file = &fat_ops_file;
fsnode_root->ops_dir = &fat_ops_dir;
fsnode_root->destructor = fat_node_destructor;
fsnode_root->new_opened_file = fat_new_opened_file;
fsnode_root->close_opened_file = fat_close_opened_file;
fsnode_root->custom_data = NULL;
/* Initialize ondisk_lnk_cnt for root directory. */
sos_si64_t offset;
sos_ui64_t storage_location = fsnode_root->storage_location;
retval = sos_fat_helper_get_next_directory_entry(device->block_device,
data_struct,
storage_location,
NULL,
&storage_location,
&offset);
if (retval != SOS_OK && retval != -SOS_ENOENT) {
sos_hash_dispose(hash);
sos_kfree((sos_vaddr_t) fs);
sos_kfree((sos_vaddr_t) fsnode_root);
return retval;
}
/* Count the number of entries in the root directory to initialize
* the ondisk_lnk_cnt field. */
while (retval != -SOS_ENOENT) {
fsnode_root->ondisk_lnk_cnt++;
storage_location += sizeof(struct sos_fat_directory_entry);
retval = sos_fat_helper_get_next_directory_entry(device->block_device,
data_struct,
storage_location,
NULL,
&storage_location,
&offset);
if (retval != SOS_OK && retval != -SOS_ENOENT) {
sos_hash_dispose(hash);
sos_kfree((sos_vaddr_t) fs);
sos_kfree((sos_vaddr_t) fsnode_root);
return retval;
}
}
retval = sos_fs_register_fs_instance(fs, fsnode_root);
sos_fs_unref_fsnode(fsnode_root);
if (SOS_OK != retval)
{
sos_hash_dispose(hash);
sos_kfree((sos_vaddr_t) fs);
sos_kfree((sos_vaddr_t) fsnode_root);
return retval;
}
*mounted_fs = fs;
return SOS_OK;
}
static sos_ret_t fat_umount(struct sos_fs_manager_type * this,
struct sos_fs_manager_instance * mounted_fs)
{
struct sos_fat_data_structure * data_struct = (struct sos_fat_data_structure*)mounted_fs->custom_data;
sos_hash_dispose(mounted_fs->nodecache);
sos_kfree((sos_vaddr_t)data_struct);
sos_fs_unregister_fs_instance(mounted_fs);
sos_kfree((sos_vaddr_t)mounted_fs);
return SOS_OK;
}
/* }}} */
/* ********************************************************
* FS register {{{
*/
/** The initialization function of the FAT filesystem */
sos_ret_t sos_fs_fat_subsystem_setup()
{
strzcpy(fat_type.name, "FAT", SOS_FS_MANAGER_NAME_MAXLEN);
fat_type.mount = fat_mount;
fat_type.umount = fat_umount;
return sos_fs_register_fs_type(&fat_type);
}
/* }}} */