3213 lines
98 KiB
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, §or_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, §or_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, §or_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, §or_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, §or_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, §or_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, §or_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, §or_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, §or_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,
|
|
¤t_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,
|
|
§or_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, §or_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)),
|
|
¤t_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);
|
|
}
|
|
/* }}} */
|
|
|