From e400c8c9027c5fe9f0408f91eb49f7575b05dcfe Mon Sep 17 00:00:00 2001 From: savelij13 Date: Thu, 11 Sep 2025 09:13:15 +0300 Subject: [PATCH] fatfs v0.05a Feb 03, 2008: - Added f_truncate(). - Added f_utime(). - Fixed off by one error at FAT sub-type determination. - Fixed btr in f_read() can be mistruncated. - Fixed cached sector is not flushed when create and close without write. --- doc/00index_e.html | 28 +- doc/00index_j.html | 15 +- doc/css_e.css | 1 + doc/css_j.css | 1 + doc/en/appnote.html | 40 +-- doc/en/dioctl.html | 2 +- doc/en/dread.html | 4 +- doc/en/dstat.html | 6 +- doc/en/dwrite.html | 4 +- doc/en/fattime.html | 4 +- doc/en/filename.html | 4 +- doc/en/getfree.html | 4 +- doc/en/lseek.html | 5 +- doc/en/mkfs.html | 2 +- doc/en/mount.html | 4 +- doc/en/open.html | 9 +- doc/en/read.html | 2 +- doc/en/sfileinfo.html | 34 +- doc/en/truncate.html | 63 ++++ doc/en/unlink.html | 3 +- doc/en/utime.html | 75 ++++ doc/en/write.html | 2 +- doc/img/f4.png | Bin 2474 -> 2335 bytes doc/img/f5.png | Bin 2433 -> 2479 bytes doc/img/rwtest.png | Bin 19261 -> 20474 bytes doc/ja/appnote.html | 44 +-- doc/ja/getfree.html | 2 +- doc/ja/open.html | 2 +- doc/ja/sfileinfo.html | 22 +- doc/ja/truncate.html | 63 ++++ doc/ja/utime.html | 75 ++++ doc/updates.txt | 9 +- src/00readme.txt | 25 +- src/diskio.c | 10 +- src/diskio.h | 2 +- src/ff.c | 788 +++++++++++++++++++++++------------------- src/ff.h | 24 +- src/integer.h | 12 +- src/tff.c | 578 +++++++++++++++++-------------- src/tff.h | 20 +- 40 files changed, 1238 insertions(+), 750 deletions(-) create mode 100644 doc/en/truncate.html create mode 100644 doc/en/utime.html create mode 100644 doc/ja/truncate.html create mode 100644 doc/ja/utime.html diff --git a/doc/00index_e.html b/doc/00index_e.html index 85473a6..33e84f5 100644 --- a/doc/00index_e.html +++ b/doc/00index_e.html @@ -3,9 +3,11 @@ + + -ELM - Generic FAT File System Module +ELM - FAT File System Module @@ -14,18 +16,18 @@
layer -

FatFs module is an experimental project to implement the FAT file system to small embedded systems. The FatFs module is written in compliance with ANSI C, therefore it is independent of hardware architecture. It can be incorporated into cheap microcontrollers, such as 8051, PIC, AVR, SH, Z80, H8, ARM and etc..., without any change.

+

FatFs is a generic file system module to implement the FAT file system to small embedded systems. The FatFs is written in compliance with ANSI C, therefore it is independent of hardware architecture. It can be incorporated into cheap microcontrollers, such as 8051, PIC, AVR, SH, Z80, H8, ARM and etc..., without any change.

-

Features of FatFs Module

+

Features of FatFs

    -
  1. Separated buffer for FAT structure and each file, suitable for fast multiple file accsess.
  2. -
  3. Supports multiple drives/partitions.
  4. +
  5. Separated buffer for FAT structure and each file, suitable for fast multiple file access.
  6. +
  7. Supports multiple drives and partitions.
  8. Supports FAT12, FAT16 and FAT32.
  9. Supports 8.3 format file name. (LFN is not supported)
  10. Supports two partitioning rules: FDISK and Super-floppy.
  11. Optimized for 8/16-bit microcontrollers.
-

Features of Tiny-FatFs Module (different to FatFs)

+

Features of Tiny-FatFs (different to FatFs)

  1. Very low memory consumption, suitable for small memory system. (1KB RAM)
  2. Supports only single drive.
  3. @@ -37,12 +39,13 @@

    Application Interface

    FatFs/Tiny-FatFs module provides following functions.

    @@ -73,8 +77,9 @@

    Resources

    -

    The FatFs/Tiny-FatFs module is a free software and is opened for education, research and development. You can use, modify and/or republish it for personal, non-profit or profit use without any restriction under your responsibility.

    +

    The FatFs/Tiny-FatFs module is a free software and is opened for education, research and development. You can use, modify and/or republish it for personal, non-profit or commercial use without any restriction under your responsibility.

    + +
    +

    FatFs Home Page

    diff --git a/doc/00index_j.html b/doc/00index_j.html index fcf63da..bd200d3 100644 --- a/doc/00index_j.html +++ b/doc/00index_j.html @@ -3,7 +3,9 @@ - + + + ELM - 汎用FATファイルシステム・モジュール @@ -42,15 +44,17 @@
  4. f_read - ファイルの読み込み
  5. f_write - ファイルの書き込み
  6. f_lseek - ファイルR/Wポインタの移動
  7. +
  8. f_truncate - ファイル・サイズの切り詰め
  9. f_sync - キャッシュされたデータのフラッシュ
  10. f_opendir - ディレクトリのオープン
  11. f_readdir - ディレクトリの読み出し
  12. f_getfree - ディスク空き領域の取得
  13. f_stat - ファイル・ステータスの取得
  14. f_mkdir - ディレクトリの作成
  15. -
  16. f_unlink - ファイルまたはディレクトリの削除
  17. -
  18. f_chmod - ファイルまたはディレクトリ属性の変更
  19. -
  20. f_rename - ファイルまたはディレクトリの名前変更・移動
  21. +
  22. f_unlink - ファイル/ディレクトリの削除
  23. +
  24. f_chmod - ファイル/ディレクトリの属性の変更
  25. +
  26. f_utime - ファイル/ディレクトリのタイムスタンプの変更
  27. +
  28. f_rename - ファイル/ディレクトリの名前変更・移動
  29. f_mkfs - ディスクのフォーマット
@@ -74,6 +78,7 @@

資料

FatFs/Tiny-FatFsモジュールはフリー・ソフトウェアとして教育・研究・開発用に公開しています。どのような利用目的(個人・非商用・商用)でも使用・改変・配布について一切の制限はありませんが、全て利用者の責任の下での利用とします。

Each case does not affect the files that not in write operation. To minimize risk of data loss, the critical section can be minimized like shown in Figure 5 by minimizing the time that file is opened in write mode and using f_sync function properly.

diff --git a/doc/en/dioctl.html b/doc/en/dioctl.html index ca83836..3536040 100644 --- a/doc/en/dioctl.html +++ b/doc/en/dioctl.html @@ -55,7 +55,7 @@ DRESULT disk_ioctl (

The FatFs module uses only device independent commands described below. Any device dependent function is not used.

- +
CommandDescription
CTRL_SYNCMake sure that the disk drive has finished pending write process. When the module has a write back cache, flush the dirty sector immediately. In read-only configuration, this command is not needed.
CTRL_SYNCMake sure that the disk drive has finished pending write process. When the disk I/O module has a write back cache, flush the dirty sector immediately. In read-only configuration, this command is not needed.
GET_SECTOR_COUNTReturns total sectors on the drive into the DWORD variable pointed by Buffer. This command is used in only f_mkfs function.
GET_BLOCK_SIZEReturns erase block size of the memory array in unit of sector into the DWORD variable pointed by Buffer. When the erase block size is unknown or magnetic disk device, return 1. This command is used in only f_mkfs function.
diff --git a/doc/en/dread.html b/doc/en/dread.html index e81b3cb..8766939 100644 --- a/doc/en/dread.html +++ b/doc/en/dread.html @@ -16,7 +16,7 @@
 DRESULT disk_read (
   BYTE Drive,          /* Physical drive number */
-  BYTE* Buffer,        /* Pointer to the read buffer */
+  BYTE* Buffer,        /* Pointer to the read data buffer */
   DWORD SectorNumber,  /* Sector number to read from */
   BYTE SectorCount     /* Number of sectros to read */
 );
@@ -31,7 +31,7 @@ DRESULT disk_read (
 
Buffer
Pointer to the read buffer to store the read data. The buffer size of number of bytes to be read is required.
SectorNumber
-
Specifies the start sector number in logical block address.
+
Specifies the start sector number in logical block address (LBA).
SectorCount
Specifies number of sectors to read. The value can be 1 to 255.
diff --git a/doc/en/dstat.html b/doc/en/dstat.html index 54e716f..b0cfb58 100644 --- a/doc/en/dstat.html +++ b/doc/en/dstat.html @@ -12,7 +12,7 @@

disk_status

-

The disk_status function return the current disk status.

+

The disk_status function returns the current disk status.

 DSTATUS disk_status (
   BYTE Drive     /* Physical drive number */
@@ -24,7 +24,7 @@ DSTATUS disk_status (
 

Parameters

Drive
-
Specifies the physical drive number to be tested.
+
Specifies the physical drive number to be confirmed.
@@ -38,7 +38,7 @@ DSTATUS disk_status (
STA_NODISK
Indicates that no medium in the drive. This is always cleared on fixed disk drive.
STA_PROTECTED
-
Indicates that the medium is write protected. This is always cleared on the drive that does not support write protect notch.
+
Indicates that the medium is write protected. This is always cleared on the drive that does not support write protect notch. Not valid when STA_NODISK is set.
diff --git a/doc/en/dwrite.html b/doc/en/dwrite.html index ff73522..4d8b87c 100644 --- a/doc/en/dwrite.html +++ b/doc/en/dwrite.html @@ -16,7 +16,7 @@
 DRESULT disk_write (
   BYTE Drive,          /* Physical drive number */
-  const BYTE* Buffer,  /* Pointer to the read buffer */
+  const BYTE* Buffer,  /* Pointer to the write data */
   DWORD SectorNumber,  /* Sector number to write */
   BYTE SectorCount     /* Number of sectors to write */
 );
@@ -31,7 +31,7 @@ DRESULT disk_write (
 
Buffer
Pointer to the data to be written.
SectorNumber -
Specifies the start sector number in logical block address.
+
Specifies the start sector number in logical block address (LBA).
SectorCount
Specifies the number of sectors to write. The value can be 1 to 255.
diff --git a/doc/en/fattime.html b/doc/en/fattime.html index 39081ab..01bda3d 100644 --- a/doc/en/fattime.html +++ b/doc/en/fattime.html @@ -28,13 +28,13 @@ DWORD get_fattime (void);
bit24:21
Month (1..12)
bit20:16
-
Date (1..31)
+
Day in month(1..31)
bit15:11
Hour (0..23)
bit10:5
Minute (0..59)
bit4:0
-
Second/2 (0..29)
+
Second / 2 (0..29)
diff --git a/doc/en/filename.html b/doc/en/filename.html index 20127d2..ab54249 100644 --- a/doc/en/filename.html +++ b/doc/en/filename.html @@ -34,10 +34,10 @@


Correspondence between logical/physical drive

-

In default, the FatFs module has work areas that called `file system object' for each logical drive. The logical drive is bound simply to the physical drive that has same drive number, and first partition is mounted. When _MULTI_PARTITION is specified in configuration option, each individual logical drive can be bound to any physical drive/partition. In this case, a drive number resolution table must be defined as follows:

+

In default, the FatFs module has work areas that called file system object for each logical drive. The logical drive is bound to the physical drive that has same drive number, and the first partition is mounted. When _MULTI_PARTITION is specified in configuration option, each individual logical drive can be bound to any physical drive/partition. In this case, a drive number resolution table must be defined as follows:

 Example: Logical drive 0-2 are assigned to three pri-partitions on the physical drive 0 (fixed disk)
-         Logical drive 3 is assigned to physical drive 0 (removable disk)
+         Logical drive 3 is assigned to physical drive 1 (removable disk)
 
 const PARTITION Drives[] = {
     {0, 0},     /* Logical drive 0 ==> Physical drive 0, 1st partition */
diff --git a/doc/en/getfree.html b/doc/en/getfree.html
index 7d25fe0..ab91028 100644
--- a/doc/en/getfree.html
+++ b/doc/en/getfree.html
@@ -30,7 +30,7 @@ FRESULT f_getfree (
 
Clusters
Pointer to the DWORD variable to store number of free clusters.
FileSystemObject
-
Pointer to the pointer that to be stored the pointer to corresponding file system object.
+
Pointer to pointer that to store a pointer to the corresponding file system object.
@@ -56,7 +56,7 @@ FRESULT f_getfree (

Descriptions

-

The f_getfree function gets number of free clusters on the drive. The sects_clust member in the file system object refreting number of sectors per cluster, so that the free space in unit of sector can be calcurated with this. When _USE_FSINFO option is enabled, this function can return inaccurate free cluster count on FAT32 volume. When _USE_FSINFO option is disabled, this function will take a time on FAT32 volume.

+

The f_getfree function gets number of free clusters on the drive. The sects_clust member in the file system object is refreting number of sectors per cluster, so that the free space in unit of sector can be calcurated with this. When _USE_FSINFO option is enabled, this function might return an inaccurate free cluster count on FAT32 volume. When it is disabled, this function will take a time on FAT32 volume.

This function is not supported in read-only configuration and minimization level of >= 1.

diff --git a/doc/en/lseek.html b/doc/en/lseek.html index 1d76eea..0a81d71 100644 --- a/doc/en/lseek.html +++ b/doc/en/lseek.html @@ -12,7 +12,8 @@

f_lseek

-

The f_lseek functione moves the file read/write pointer of an open file object.

+

The f_lseek function moves the file read/write pointer of an open file object. It can also be used to extend the file size (cluster pre-allocation).

+
 FRESULT f_lseek (
   FIL* FileObject,   /* Pointer to the file object structure *
@@ -78,7 +79,7 @@ FRESULT f_lseek (
 
 

References

-

f_open, FIL

+

f_open, f_truncate, FIL

Return

diff --git a/doc/en/mkfs.html b/doc/en/mkfs.html index 57d9f63..1c98f57 100644 --- a/doc/en/mkfs.html +++ b/doc/en/mkfs.html @@ -63,7 +63,7 @@ FRESULT f_mkfs (

Description

The f_mkfs function creates a FAT file system on the drive. There are two partitioning rules, FDISK and SFD, for removable media. It can be selected with an argument. The FDISK format is recommended for most case. This function currently does not support multiple partition, so that existing partitions on the physical dirve will be deleted and re-created a partition occupies entire disk space.

-

The FAT type, FAT12/FAT16/FAT32, is determined by only how many clusters on the drive and nothing else, according to the FAT specification issued by Microsoft. Thus which FAT type is selected, is depends on the drive size and specified cluster size. The cluster size affects performance of file system and large cluster increases the performance, so that 32768 bytes per cluster is recommended for most case except for small drive.

+

The FAT sub-type, FAT12/FAT16/FAT32, is determined by only how many clusters on the drive and nothing else, according to the FAT specification issued by Microsoft. Thus which FAT sub-type is selected, is depends on the drive size and specified cluster size. The cluster size affects performance of file system and large cluster increases the performance, so that 32768 bytes per cluster is recommended for most case except for small drive.

This function is supported on only FatFs with _USE_MKFS option.

diff --git a/doc/en/mount.html b/doc/en/mount.html index 72ea811..aed5691 100644 --- a/doc/en/mount.html +++ b/doc/en/mount.html @@ -44,8 +44,8 @@ FRESULT f_mount (

Description

-

The f_mount function registers/unregisters a work area to the FatFs module. The work area must be given to the logical drive with this function before using any file function. To unregister a work area, specify a NULL to the FileSystemObject, and then the work area can be discarded.

-

This function only initializes the work area and registers its address to the internal table, any access to the disk I/O layer does not occure. Actual mounting process is performed in any other file funcitons with path name when it is needed.

+

The f_mount function registers/unregisters a work area to the FatFs module. The work area must be given to the logical drive with this function before any other file function. To unregister a work area, specify a NULL to the FileSystemObject, and then the work area can be discarded.

+

This function only initializes the work area and registers its address to the internal table, any access to the disk I/O layer does not occure. The actual mounting process is performed depends on requirement in any other file funcitons with path name.

diff --git a/doc/en/open.html b/doc/en/open.html index 43c0d73..c44488b 100644 --- a/doc/en/open.html +++ b/doc/en/open.html @@ -61,7 +61,10 @@ FRESULT f_open (
FR_EXIST
The file is already existing.
FR_DENIED
-
The required access was denied due to any of following reasons: write mode open of a file that has read-only attribute, file creation under existing a same name directory or read-only file, cannot be created due to the directory table or disk full.
+
The required access was denied due to one of the following reasons: +
  • Write mode open of a read-only file.
  • +
  • File cannot be created due to a read-only file or same name directory is existing.
  • +
  • File cannot be created due to the directory table or disk is full.
FR_NOT_READY
The disk drive cannot work due to no medium in the drive or any other reason.
FR_WRITE_PROTECTED
@@ -78,8 +81,8 @@ FRESULT f_open (

Description

-

The created file object is used for subsequent calls to refer to the file. When close an open file object, use f_close function.

-

Before using any file function, work area (file system object) must be given to each logical drive with f_mount function. All file functions can work after this procedure.

+

The created file object is used for subsequent calls to refer to the file. When close an open file object, use f_close function. If modified file is not closed correctly, the file will be collapsed.

+

Before using any file function, a work area (file system object) must be given to the logical drive with f_mount function. All file functions can work after this procedure.

The mode flags, FA_WRITE, FA_CREATE_ALWAYS, FA_CREATE_NEW, FA_OPEN_ALWAYS, are not supported in read-only configuration.

diff --git a/doc/en/read.html b/doc/en/read.html index 21765a9..fa4984e 100644 --- a/doc/en/read.html +++ b/doc/en/read.html @@ -44,7 +44,7 @@ FRESULT f_read (
FR_OK (0)
The function succeeded.
FR_DENIED
-
The function denied due to the file has been opened in write only mode.
+
The function denied due to the file has been opened in non-read mode.
FR_RW_ERROR
The function failed due to a disk error or an internal error.
FR_NOT_READY
diff --git a/doc/en/sfileinfo.html b/doc/en/sfileinfo.html index 0d18636..a0dc8f7 100644 --- a/doc/en/sfileinfo.html +++ b/doc/en/sfileinfo.html @@ -15,11 +15,11 @@

The FILINFO structure holds a file information returned by f_stat() and f_readdir().

 typedef struct _FILINFO {
-    DWORD fsize;            // Size
-    WORD fdate;             // Date
-    WORD ftime;             // Time
-    BYTE fattrib;           // Attribute
-    char fname[8+1+3+1];    // Name
+    DWORD fsize;            /* Size */
+    WORD fdate;             /* Date */
+    WORD ftime;             /* Time */
+    BYTE fattrib;           /* Attribute */
+    char fname[8+1+3+1];    /* Name */
 } FILINFO;
 
@@ -29,11 +29,29 @@ typedef struct _FILINFO {
fsize
Indicates size of the file in unit of byte. This is always zero when it is a directory.
fdate
-
Indicates the date that the file was modified or the directory was created.
+
Indicates the date that the file was modified or the directory was created.
+
+
bit15:9
+
Year from 1980 (0..127)
+
bit8:5
+
Month (1..12)
+
bit4:0
+
Day (1..31)
+
+
ftime
-
Indicates the time that the file was modified or the directory was created.
+
Indicates the time that the file was modified or the directory was created.
+
+
bit15:11
+
Hour (0..23)
+
bit10:5
+
Minute (0..59)
+
bit4:0
+
Second / 2 (0..29)
+
+
fattrib
-
Indicates the file/directory attribute in combination of AM_DIR, AM_RDO, AM_HID, AM_SYS and AM_ARC.
+
Indicates the file/directory attribute in combination of AM_DIR, AM_RDO, AM_HID, AM_SYS and AM_ARC.
fname[]
Indicates the file/directory name in 8.3 format null-terminated string.
diff --git a/doc/en/truncate.html b/doc/en/truncate.html new file mode 100644 index 0000000..847f3b1 --- /dev/null +++ b/doc/en/truncate.html @@ -0,0 +1,63 @@ + + + + + + + +FatFs - f_truncate + + + + +
+

f_truncate

+

The f_truncate function truncates the file size.

+
+FRESULT f_truncate (
+  FIL* FileObject     /* Pointer to the file object */
+);
+
+
+ +
+

Parameters

+
+
FileObject
+
Pointer to the open file object to be truncated.
+
+
+ + +
+

Return Values

+
+
FR_OK (0)
+
The function succeeded.
+
FR_DENIED
+
The file has been opened in read-only mode.
+
FR_RW_ERROR
+
The function failed due to a disk error or an internal error.
+
FR_NOT_READY
+
The disk drive cannot work due to no medium in the drive or any other reason.
+
FR_INVALID_OBJECT
+
The file object is invalid.
+
+
+ + +
+

Description

+

The f_truncate function truncates the file size to the current file R/W point. When the file R/W pointer is pointing end of the file, there is no effect. This function is not supported in read-only configuration and minimization level of >=1.

+
+ + +
+

References

+

f_open, f_lseek, FIL

+
+ + +

Return

+ + diff --git a/doc/en/unlink.html b/doc/en/unlink.html index 9346296..33a0e73 100644 --- a/doc/en/unlink.html +++ b/doc/en/unlink.html @@ -43,7 +43,8 @@ FRESULT f_unlink (
FR_INVALID_DRIVE
The drive number is invalid.
FR_DENIED
-
The function was denied due to either of following reasons: the file or directory has read-only attribute, the directory is not empty.
+
The function was denied due to either of following reasons: +
  • The file or directory has read-only attribute
  • The directory is not empty.
FR_NOT_READY
The disk drive cannot work due to no medium in the drive or any other reason.
FR_WRITE_PROTECTED
diff --git a/doc/en/utime.html b/doc/en/utime.html new file mode 100644 index 0000000..b203581 --- /dev/null +++ b/doc/en/utime.html @@ -0,0 +1,75 @@ + + + + + + + +FatFs - f_utime + + + + +
+

f_utime

+

The f_utime function changes the timestamp of a file or directory.

+
+FRESULT f_utime (
+  const char* FileName,    /* Pointer to the file or directory path */
+  const FILINFO* TimeDate  /* Time and data to be set */
+);
+
+
+ +
+

Parameter

+
+
FileName
+
Pointer to the null-terminated string that specifies a file or directory to be changed.
+
TimeDate
+
Pointer to the file information structure that has a timestamp to be set in member fdate and ftime. Do not care any other members.
+
+
+ + +
+

Return Values

+
+
FR_OK (0)
+
The function succeeded.
+
FR_NO_FILE
+
Could not find the file.
+
FR_NO_PATH
+
Could not find the path.
+
FR_INVALID_NAME
+
The file name is invalid.
+
FR_INVALID_DRIVE
+
The drive number is invalid.
+
FR_NOT_READY
+
The disk drive cannot work due to no medium in the drive or any other reason.
+
FR_WRITE_PROTECTED
+
The medium is write protected.
+
FR_RW_ERROR
+
The function failed due to a disk error or an internal error.
+
FR_NOT_ENABLED
+
The logical drive has no work area.
+
FR_NO_FILESYSTEM
+
There is no valid FAT partition on the disk.
+
+
+ + +
+

Description

+

The f_utime function changes the timestamp of a file or directory. This function is not supported in read-only configuration and minimization level of >=1.

+
+ + +
+

References

+

f_stat, FILINFO

+
+ +

Return

+ + diff --git a/doc/en/write.html b/doc/en/write.html index 8fd3d5c..3e14a5b 100644 --- a/doc/en/write.html +++ b/doc/en/write.html @@ -44,7 +44,7 @@ FRESULT f_write (
FR_OK (0)
The function succeeded.
FR_DENIED
-
The function denied due to the file has been opened in read only mode.
+
The function denied due to the file has been opened in non-write mode.
FR_RW_ERROR
The function failed due to a disk error or an internal error.
FR_NOT_READY
diff --git a/doc/img/f4.png b/doc/img/f4.png index 35716ff67b4b662b1a3d0e92130255629bfe39a5..f9a6b46487f09814439ec405bce3114a55be0025 100644 GIT binary patch literal 2335 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 MX9tXZm90PNA3>mBJ^%m! diff --git a/doc/img/f5.png b/doc/img/f5.png index 855917afca8988249c3500bbaa09d819c6f39178..b110b291e3225ff675f118ee8134865f251602d3 100644 GIT binary patch literal 2479 zcma);cTm&W7RON%F<>yXgdUVoqzKA_w4i|qgis}FC?c%VivdJHgseYB2r7u7DzXv8 zMUW;Xp@~tHP?Wv`QUZz~L?DEwAbGg&InSN9f4n>Qe$ULAduQ%BGoSCh6i0h2sIaWC zfPerLXN^53Ah7Mo)~W&tZe=PO-)C$ET}NBO(XIIZ48DK>pT9Nv=nz-&tpgB}wM)2w zfN1mgwJlv!RBkJ$g2Q46Q8}OQK{JZgVU~e{m%q(x$Yq5SF`}B%R-2OB_pTs7Q+=P` z>U7gauN5|@fwid?`QSI67)dzR>mQ^Qw4D0HEN9y0p)$UpqXP%hA2dQ0JZfLI!iVWO z^CD~z{ESgx)FQCajC!#|CN&qSNPCsT%$gxWj9^&0rVyrQ4^Qg@37s#Cm@xkCUw~rt% zyzpwr2?ZC-&?dwBmgwe$9c&#QAAdFJ(!*oHAeB%;ejT6@~~{qtzWh^zDL zHMHNl0#A4-S1vwd(>b!3N*lv*d=K;*h5atO*)uYOl2%S~Aor&R^jAzHWF{3*6}CSXMNSpjQ4^Cufo0TWtJG$%>W7XiDd+R&r#1w} z0Z5D@ZssnsYQ%>Kiw8UXEGmfMso}_z`1nPk$d*F;^X9777iZB9`xs*s!xCED4YuSu zix)uR(owP>C2wSTkdh9K3pzwlnSeS!5<`}YZiz{80PXHIf6$o#nOQpFw_e?9(CZG# z2>X56F0n&O#LI>+KeMaOQR*9!NN?e$&JeNjg)S$X~Bz9R5$Y z|7!UUNkPafN^F=g?K6ivK?yCOLn6sZ5VQKFiW|w05V*XQ#HF||!6aK!CA%<-O%3d8 zU&vw?CtQ7Z20)UFPV;OOPK+NQRnDUhUjnVDH` zlBer2LMAk4=U0%0Y9(ynU3c{l`=qjySa4Bz{L7J|)^Cu>v;KB~Q_{m*9F|&8-CiPb zqtbhxum_=mDkNAJ$}Fb&0584!yC*10$DeRFak?k#P}17UX3>Gq0F9kJ8M8$L%|$h9 zWgz-T*d7&smh&Bm&$*>j<>ibq+;E5M^U1@=!pax6HPCL8*Z%i1-m`TK5%M|lv)rkP z@p7fM>bVm~aWv4l~@;W-nj9H?gw+>FGJ7z{bC8{hPyWmCggv&FWW>Qxly>o)DjCW_#vNp0~m9 zybcbZRj1n;8fy;n6~dGXzj@27hPoqk!fs0SahHKx#aalCg9mtS(Ncp*kVO$6Gr#FK z`j*QMHD9laoEd6xMP)WpoS9=D*J@G3kL$YO7Me(8A-;nop9B48tBrnx zegU#8kKvUIc;?2L*$ZUCJx+M2G%VG;E2GI|I#2|9rsp%Vf|p(kP2EN3REt~_v6?e0 zF2nFP)rrJxGA?8_{1n3dx$z4_YBpPDJ^s}tZ;I#g#6T^3@olIW9`N$UyIzr!y&DCI zabIU7c7NYMM%=K3s;pj99U7Z#j}H~gZ+HCCwqY+GGFac!+z-vDiEB$0Rz{*5U>)#5P&# zhYsLa9Fz`^SF^`v_Jt8kSQ2Or|H!np)fEGgt?-=5qC zq@hjtoT9XzAp7?Ct{k&nzu!8%N zGrfm@nKUeKxfTpT#^qgw^0Mt5IRH|w^*D_48jLw`S1DGcPIcwJlg*8QUz@0p*TE(H zjx^{*SgL!r=jz&%Vxa)8es^QK6LXHlfp6hvn%92V=)W!FZ&MOxOa`CWE&u)7g*$4G JEj9N9{sm42e|`V} literal 2433 zcma)8c{tQv8=kIadKsm$WLhNaWL_d<-x`s@Sd)YpLP;1z!jPXPCY8o6WvOhHvAkn9 zc}KElC&XCBP9|d)V_xrfeb@E%y7%TJUM#*ml=My-cF}Wx1pE8%CsO=vQM@uoqnrr$^VkrYdcgLY((&FQ z7*W9u)jHXEGeW`^Mu1rU3ZN)z?)$qps;p+4?2y$sew3*DVHK5<-BDr63d(FpCSV?p7=!PN4K8y`nC-JH{`BKxrXWohu>8 zs?-p>W3Za2N0}ad#1YVGtNf3Ev}|+N zlxO)$@I73p(crBNa>dEc!41-DgNxO(AQhtcjXO1v<@N`ngQo>EyC+phkR zqTY{XtPH-Jp&mbHdRQkBo z0=50DMAk+yq zB7m#i-x)`dR0vT}Y?rSw$9X!Nwi|2@!m?&Q-(QK9oBKm}@M6^bVvJ`iXa^#qngPLAAdwo%AN zSpp)qtACN`mm!0ZXH>%G+ ze$d#y9i*Gfh9iS(uzyY!*cQGWdduTvy!x4jD>x7k$M3?f^C-fWl$%=Q09A33<7J3; zd1h3hex2T3my7GRbE%SyuSx5{&z>5I%W*Tqvd)oTkBm1s@NqUa7`wkBU&|2}gjxCy z*1YDY%P%Y<5Ne0B#w>MZ$GY9YEP2o@6=;pgqX!zn8A8HG1VV97f{*Rhl$um%BQ{w+ z0U!Utk25M^3zBQ@`3X2`2;A$wbQxcf)?j`v2Dw~$S^>qPp2wROa!pGa_|uyruuU`Q zx*h4{xO3CUBZNoNHcy2T``9_n9PYJYN-jA5af(8@YtyA*gQAijH`+$>UF59M7WpX_ zO4G5&54`bCgx(?osvs| z$vV!aVcphn&o&7K4_d>Sc`@EO15vXtrzvfzVM}mf9$vHHADbJD@gmWd(w>6oftOqA z$>!xP*=yppj7d#mN1B&eszV_sSCEMZ*YZE(DUBDyQ!-CQoDb??^ipGNC%<`iz9f8> znQc#!E?dp;`+S$*n$?T8y|O)6x2_r9tT1YH*1-DI?mXpI>0uW0Pd33I^2VCxTh-9~>06S2;#1z2^YmLC7-)G@h(^kba6CP4Y>*YaYF)Q7$ zL1D=HW{L`xaMA7V&ZlQ;+Gd@jZF9HIj-0N1a6V7!P=^U5yrnp^*0eh+zPZA-p(wu+ zT#6BzKBJvSWV6$(8nLQACnxBxT7Gr@tWUTZda-)jy8TB+16^Z~_ux0d^1G}xKMrUt zHe6nW_kSd-WscLn(LX-n@Z%WT#w0KsCi}+=GvrNOOhekisps5=aH;KDy+Ck4h3tgx z(}j5haxtJmb%gmRCIW2ayFZ;qnjr6tBf;;KE3|XV587Nv3nDSit|ecInJiKEmD?O@ zJ(~<(2)(PMN?8swymtLz7*6F^-fat;iU2+;d7WHz4azBaF`s={1P|vE&hS{Eh{k@1 zfiBNh=c+M9gI4J(|8U-aFPF7wi#=Ue>g(6W%epfkD(>j@Y{gzCr->o^xo)uKdTr9!Pl94m>s0oK6B_)khr=-!4@q$V z#8=0uR)Q~Ie5=J&Lmi6WXRx}vzU}0f^$(4)detCWNFLWn_;-gGqV@dGN^aQ!)_buo zlRL_4c4J&WyVbwMdGG$%tX7uk9#urZQ{-O8$9pGarpR5QioXNm;gnR2cpjrjn<4Sd zR=j2E0=yJ;LnEizn+p+HQX#oGgf(VV58zPA#v`#X37sP}&71vWtH8Yd5_TZn9z20^ z!Tmy$ z&j+_XXqrtu_FUXmAjZ{5|gOW8;5qwFRbQ?rRxZ{8QIYk+aM#^Ib$I=hL?x8QWVv0@yv0)P sp(a|=n7D63do5Cnkr3|ppC`Y&e_!Im*^%63zP~?GMh0g3WIZhK7t<+usQ>@~ diff --git a/doc/img/rwtest.png b/doc/img/rwtest.png index d2e646f20d61bab39f741921a4b450b8ceeea4eb..f3cacfbb41ca313729086cc9a93bee4d55f026db 100644 GIT binary patch literal 20474 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 F_J0W6N0k5o literal 19261 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%0D(;M_ zFKlj=eqn6^iOde91?3ND>7QA2!H8*CZz}vl+~E?a3#g1@DgXUilLS)zmu@`*EsREY z5MI?h9lpe{+ZL^gqK&!aW4n#|l$B;}yw6zBjf%Mu2p6&DJy7}&kfnXKu)7<-+>N-& zsGDJ(?(oG1RsErBM488^-|yWx&F1I1!1n`L!7|69~K>l-PSd?P#`omZ*(p1 z^Ke}9a`KMr(n4*Wea!M4-VJ$7vAHB1kMqaV3Y z&4USe^^z2nZdQb$sDxFkJ~H)VxrbZwuO3=qehu zrb=sX4Xo_Ss;>X^M-)s= zH`DKI-Nn0hZ*l0JjG%}40kI`xccz{`9@TY{o{JkTUTfaw1Lp@lhZif)j@xDqiG7u3 zuVsI&yZ%zEw1b>g29=U<@vBd+3O~89sKOaYJjU56tlWDBzec9fQGFi29$hhVwJv)>Ev?OMoQTSgMvXKR#C?}-h}1I;*kQ*oJTEZJbUKO*`$Cw5OrXbAT3a12C6 zYo)85gk_Y=5Jj4A-ylWIkzV@RZu9vKT;fT7f zdswdhZ9ZL_Kj{yfZyEsQ8=MWZ`<|qmlo54kg6LmkTlDGMud|8xR2Lh%@zR7&chHU# zCDV8#zBFVOUxHVb3TL6D4!ylup|f2Q)Og?=U&msV%+~ZBMr5{+v3dj=-waF~P{FI0 z<9}O9s<(P+*X;V{WT-GhHtPeYv35=dVN9{Z5ioS)>NcTn*t+S~ZjLK4a8<-hS>K$e zmH767g@&$Qz2vmZ{fX|*xI%rU9dxQHJm*T=jSaV6I^ZMC_C1a9-ZM4!9abUfx$G*9 zEcy8Ub6vJbKjAoL8Gm2^Wc%9Ye(@pCY$^e1`mx3z{i%m8y)TfMhr51pz^%mXfjq|z ziU0Ah1af>46)wI!hkobA zFdg~v$xy*9ER{(yxWOe`dc%!AOaU3dMjs^+GM&w~f@; zj0!X^T1^I?f^p!95u1zeT9+f$AI$9bIjbq8+X~3*+B@xOT`^Oj=|k6c50|~HwkjOQ z)i)%}E7hC7c-eKuPg5q`)aKGmCd{S
  • 処理系はANSI C準拠であること。
    FatFsモジュールはANSI C準拠で記述されているので、ANSI C準拠のコンパイラなら特に処理系依存な点はありません。ただし、FATというシステム間でポータブルな構造体を操作するため、プロセッサのエンディアンの違いは意識する必要があります。これは ff.h(tff.h) で定義されているので、最初にこれを適切に設定します(忘れている場合はエラーを出す)。
  • -
  • char/short/long のサイズは、それぞれ 8/16/32ビット で、intは 16ビット以上 であること。
    -これは integer.h 内で typedef されています。整数の型とサイズに関しては、まっとうな処理系なら問題ないはずですが、既存の定義と衝突した場合は矛盾がないように注意しなければなりません。
  • +
  • char/short/long のサイズは、それぞれ 8/16/32ビット で、intは 16または32ビット であること。
    +使用される整数の型は integer.h 内で typedef されています。整数の型とサイズに関しては、まっとうな処理系なら問題ないはずですが、既存の定義と衝突した場合は矛盾がないように注意しなければなりません。
  • -

    メモリ使用量 (R0.05)

    -

    各種環境でのモジュールのメモリ使用量の例を示します。数値の単位はバイトで、Dは論理ドライブ数、Fは同時オープン・ファイル数を示します。最適化オプションは、全てコード・サイズとしています。

    +

    メモリ使用量 (R0.05a)

    +

    各種環境でのモジュールのメモリ使用量の例を示します。数値の単位はバイトで、Dは論理ドライブ数、Fは同時オープン・ファイル数を示します。最適化オプションはコード・サイズとしています。

    - - - - - - - - - - - - - - - + + + + + + + + + + + + + + +
    AVRH8/300HMSP430TLCS-870/CV850ESSH2
    コンパイラgccCH38CL430CC870CCA850SHC
    _MCU_ENDIAN122112
    FatFs コード
    (標準, R/W構成)
    8810888064187830
    FatFs コード
    (最小, R/W構成)
    5832574840725366
    FatFs コード
    (標準, R/O構成)
    4264411029903420
    FatFs コード
    (最小, R/O構成)
    3052312421942634
    FatFs 静的ワークD*2 + 2D*4 + 2D*4 + 2D*4 + 2
    FatFs 動的ワークD*554 + F*544D*554 + F*550D*554 + F*550D*554 + F*550
    Tiny-FatFs コード
    (標準, R/W構成)
    73767274662288545670
    Tiny-FatFs コード
    (最小, R/W構成)
    48664826435461763726
    Tiny-FatFs コード
    (標準, R/O構成)
    36543554322043582710
    Tiny-FatFs コード
    (最小, R/O構成)
    26202708240233291910
    Tiny-FatFs 静的ワーク46446
    Tiny-FatFs 動的ワーク544 + F*28544 + F*32544 + F*28544 + F*28544 + F*32
    AVRH8/300HPICMSP430TLCS-870/CV850ESSH2
    コンパイラgccCH38C30(gcc)CL430CC870CCA850SHC
    _MCU_ENDIAN1222112
    FatFs Code
    (Full, R/W)
    91469218915566627430
    FatFs Code
    (Minimum, R/W)
    58245756586140964710
    FatFs Code
    (Full, R/O)
    42684116418729863382
    FatFs Code
    (Minimum, R/O)
    30763134314322002610
    FatFs Work (Static)D*2+2D*4+2D*2+2D*4+2D*4+2
    FatFs Work (Dynamic)D*554+F*544D*554+F*550D*554+F*544D*554+F*550D*554+F*550
    Tiny-FatFs Code
    (Full, R/W)
    7566762673716894931459266580
    Tiny-FatFs Code
    (Minimum, R/W)
    4730481446834306628536704236
    Tiny-FatFs Code
    (Full, R/O)
    3564353235223226438926783022
    Tiny-FatFs Code
    (Minimum, R/O)
    2554267425952372337618562300
    Tiny-FatFs Wrok (Static)4644466
    Tiny-FatFs Work (Dynamic)544+F*28544+F*32544+F*28544+F*28544+F*28544+F*32544+F*32
    @@ -51,9 +51,9 @@ FatFs
    - + - +
    メモリ容量FATタイプ
    <= 64MBFAT12
    64MB以下FAT12
    128MB〜2GBFAT16
    >= 4GBFAT32
    4GB以上FAT32

    2GBまでのカードに限るなら、FAT32への対応は不要です。右の表にメモリ・カードの容量と規定のFATタイプ(SDメモリの場合)を示します。メモリ・カードの出荷時は、最大のパフォーマンスが出るようにデータ領域の境界が調整されたフォーマットになっています。したがって、PCでフォーマットするなどして規定と違うフォーマットになると、書き込み性能が大幅に低下する場合があるので注意が必要です。

    @@ -88,7 +88,7 @@ FatFs fig.4
    -図5. 短くしたクリチカル・セクション
    +図5. 最小化したクリチカル・セクション
    fig.5

    diff --git a/doc/ja/getfree.html b/doc/ja/getfree.html index 441345b..0f43c00 100644 --- a/doc/ja/getfree.html +++ b/doc/ja/getfree.html @@ -56,7 +56,7 @@ FRESULT f_getfree (

    解説

    -

    論理ドライブ上の空きクラスタ数を取得します。返されたファイル・システム・オブジェクトのsects_clustメンバがクラスタあたりのセクタ数を示しているので、これを元に実際の空きサイズが計算できます。FAT32ボリュームにおいて、_USE_FSINFOが指定されている場合、不正確な値を返す場合があります。指定されていない場合、処理に時間がかかります。

    +

    論理ドライブ上の空きクラスタ数を取得します。返されたファイル・システム・オブジェクトのsects_clustメンバがクラスタあたりのセクタ数を示しているので、これを元に実際の空きサイズが計算できます。FAT32ボリュームにおいて、_USE_FSINFOが指定されているときは不正確な値を返す可能性があり、指定されていないときは処理に時間がかかります。

    リードオンリー構成および_FS_MINIMIZE >= 1ではこの関数はサポートされません。

    diff --git a/doc/ja/open.html b/doc/ja/open.html index 57e13e7..1f29bd4 100644 --- a/doc/ja/open.html +++ b/doc/ja/open.html @@ -78,7 +78,7 @@ FRESULT f_open (

    解説

    -

    作成されたファイル・オブジェクトは、以降そのファイルに対するアクセスに使用します。ファイルを閉じるときは、f_close()を使用します。

    +

    作成されたファイル・オブジェクトは、以降そのファイルに対するアクセスに使用します。ファイルを閉じるときは、f_close()を使用します。書き込みが行われたファイルが閉じられなかった場合、そのファイルは破損します。

    ファイル操作関数を使用する前にまず、f_mount()を使ってそれぞれの論理ドライブにワーク・エリア(ファイル・システム・オブジェクト)を与えなければなりません。この初期化の後、その論理ドライブに対して全てのファイル関数が使えるようになります。

    リードオンリー構成では、FA_WRITE, FA_CREATE_ALWAYS, FA_CREATE_NEW, FA_OPEN_ALWAYSの各フラグはサポートされません。

    diff --git a/doc/ja/sfileinfo.html b/doc/ja/sfileinfo.html index d9d94f5..689bf6c 100644 --- a/doc/ja/sfileinfo.html +++ b/doc/ja/sfileinfo.html @@ -29,9 +29,27 @@ typedef struct _FILINFO {
    fsize
    ファイルのバイト単位のサイズが格納されます。ディレクトリの場合は常に0です。
    fdate
    -
    ファイルの変更された日付、またはディレクトリの作成された日付が格納されます。
    +
    ファイルの変更された日付、またはディレクトリの作成された日付が格納されます。
    +
    +
    bit15:9
    +
    1980年を起点とした年が 0..127 で入ります。
    +
    bit8:5
    +
    月が 1..12 の値で入ります。
    +
    bit4:0
    +
    日が 1..31 の値で入ります。
    +
    +
    ftime
    -
    ファイルの変更された時刻、またはディレクトリの作成された時刻が格納されます。
    +
    ファイルの変更された時刻、またはディレクトリの作成された時刻が格納されます。
    +
    +
    bit15:11
    +
    時が 0..23 の値で入ります。
    +
    bit10:5
    +
    分が 0..59 の値で入ります。
    +
    bit4:0
    +
    秒/2が 0..29 の値で入ります。
    +
    +
    fattrib
    属性フラグが格納されます。フラグはAM_DIR, AM_RDO, AM_HID, AM_SYS, AM_ARCの組み合わせとなります。
    fname[]
    diff --git a/doc/ja/truncate.html b/doc/ja/truncate.html new file mode 100644 index 0000000..bf07a5d --- /dev/null +++ b/doc/ja/truncate.html @@ -0,0 +1,63 @@ + + + + + + + +FatFs - f_truncate + + + + +
    +

    f_truncate

    +

    ファイル長を切り詰めます。

    +
    +FRESULT f_truncate (
    +  FIL* FileObject     /* ファイル・オブジェクトへのポインタ */
    +);
    +
    +
    + +
    +

    引数

    +
    +
    FileObject
    +
    切り詰め対象ファイルのファイル・オブジェクトへのポインタ
    +
    +
    + + +
    +

    戻り値

    +
    +
    FR_OK (0)
    +
    正常終了。
    +
    FR_DENIED
    +
    ファイルが非書き込みモードで開かれている。
    +
    FR_RW_ERROR
    +
    ディスク・エラーまたは内部エラーによる失敗。
    +
    FR_NOT_READY
    +
    メディアがセットされていないなど、物理ドライブが動作不能状態。
    +
    FR_INVALID_OBJECT
    +
    無効なファイル・オブジェクト。
    +
    +
    + + +
    +

    解説

    +

    ファイルの長さが現在のファイルR/Wポインタに切り詰められます。ファイルR/Wポインタがファイルの終端を指しているときは、この関数は何の効果も持ちません。リード・オンリー構成および_FS_MINIMIZE >= 1ではこの関数はサポートされません。

    +
    + + +
    +

    参照

    +

    f_open, f_lseek, FIL

    +
    + + +

    Return

    + + diff --git a/doc/ja/utime.html b/doc/ja/utime.html new file mode 100644 index 0000000..f46c165 --- /dev/null +++ b/doc/ja/utime.html @@ -0,0 +1,75 @@ + + + + + + + +FatFs - f_utime + + + + +
    +

    f_utime

    +

    ファイルまたはディレクトリのタイムスタンプを変更します。

    +
    +FRESULT f_utime (
    +  const char* FileName,    /* ファイルまたはディレクトリ名へのポインタ */
    +  const FILINFO* TimeDate  /* 設定する日付 */
    +);
    +
    +
    + +
    +

    引数

    +
    +
    FileName
    +
    変更対象のファイルまたはディレクトリのフルパス名の入った'\0'で終わる文字列を指定します。
    +
    TimeDate
    +
    設定する日付と時間をfdateとftimeメンバに設定されたFILINFO構造体へのポインタ。他のメンバはDon't careです。
    +
    +
    + + +
    +

    戻り値

    +
    +
    FR_OK (0)
    +
    正常終了。
    +
    FR_NO_FILE
    +
    ファイルが見つからない。
    +
    FR_NO_PATH
    +
    パスが見つからない。
    +
    FR_INVALID_NAME
    +
    パス名が不正。
    +
    FR_INVALID_NAME
    +
    ドライブ番号が不正。
    +
    FR_NOT_READY
    +
    メディアがセットされていないなど、ディスク・ドライブが動作不能状態。
    +
    FR_WRITE_PROTECTED
    +
    メディアが書き込み禁止状態。
    +
    FR_RW_ERROR
    +
    ディスク・エラーまたは内部エラーによる失敗。
    +
    FR_NOT_ENABLED
    +
    その論理ドライブにワーク・エリアが与えられていない。
    +
    FR_NO_FILESYSTEM
    +
    ディスク上に有効なFATパーテーションが見つからない。
    +
    +
    + + +
    +

    解説

    +

    ファイルまたはディレクトリのタイムスタンプを変更します。リード・オンリー構成および_FS_MINIMIZE >= 1ではこの関数はサポートされません。

    +
    + + +
    +

    参照

    +

    f_stat, FILINFO

    +
    + +

    戻る

    + + diff --git a/doc/updates.txt b/doc/updates.txt index 1e1ba1f..49b943d 100644 --- a/doc/updates.txt +++ b/doc/updates.txt @@ -1,8 +1,15 @@ +R0.05a, Feb 03, 2008 + Added f_truncate. + Added f_utime. + Fixed off by one error at FAT sub-type determination. + Fixed btr in f_read can be mistruncated. + Fixed cached sector is left not flushed when create and close without write. + R0.05, Aug 26, 2007 Changed arguments of f_read, f_write. Changed arguments of f_mkfs. (FatFs) Fixed f_mkfs on FAT32 creates incorrect FSInfo. (FatFs) - Fixed f_mkdir on FAT32 creates incorrect directory. (FatFs) + Fixed f_mkdir on FAT32 creates incorrect directory. (FatFs) R0.04b, May 05, 2007 Added _USE_NTFLAG option. diff --git a/src/00readme.txt b/src/00readme.txt index 8458f29..1934e6a 100644 --- a/src/00readme.txt +++ b/src/00readme.txt @@ -1,4 +1,4 @@ -FatFs/Tiny-FatFs Module Source Files R0.05 (C)ChaN, 2007 +FatFs/Tiny-FatFs Module Source Files R0.05a (C)ChaN, 2008 FILES @@ -67,20 +67,21 @@ CONFIGURATION OPTIONS _USE_FSINFO - When _USE_FSINFO is set to 1, FSInfo is used for FAT32 volume. - + When _USE_FSINFO is set to 1, FSInfo is used for FAT32 volume. The initial + value is 0. (FSInfo is not used) _USE_SJIS When _USE_SJIS is set to 1, Shift_JIS code set can be used as a file name, - otherwire second byte of double-byte characters will be collapted. - The initial value is 1. + otherwire second byte of double-byte characters will be collapted. The + initial value is 1. _USE_NTFLAG When _USE_NTFLAG is set to 1, upper/lower case of the file/dir name is preserved. Note that the files are always accessed in case insensitive. + The initial value is 1. _USE_MKFS @@ -90,7 +91,7 @@ CONFIGURATION OPTIONS The initial value is 0. (f_mkfs is not available) - Following table shows which function is removed by configuratin options. + Following table shows which function is removed by configuration options. _FS_MINIMIZE _FS_READONLY _USE_MKFS (1) (2) (3) (1) (0) @@ -105,9 +106,11 @@ CONFIGURATION OPTIONS f_readdir x x f_stat x x x f_getfree x x x x + f_truncate x x x x f_unlink x x x x f_mkdir x x x x f_chmod x x x x + f_utime x x x x f_rename x x x x f_mkfs x x x x x @@ -117,8 +120,8 @@ AGREEMENTS The FatFs/Tiny-FatFs module is a free software and there is no warranty. The FatFs/Tiny-FatFs module is opened for education, reserch and development. - You can use and/or modify it for personal, non-profit or profit use without - any restriction under your responsibility. + You can use and/or modify it for personal, non-profit or commercial use + without any restriction under your responsibility. @@ -162,3 +165,9 @@ REVISION HISTORY Changed arguments of f_mkfs. (FatFs) Fixed f_mkfs on FAT32 creates incorrect FSInfo. (FatFs) Fixed f_mkdir on FAT32 creates incorrect directory. (FatFs) + + Feb 03, 2008 R0.05a Added f_truncate(). + Added f_utime(). + Fixed off by one error at FAT sub-type determination. + Fixed btr in f_read() can be mistruncated. + Fixed cached sector is not flushed when create and close without write. diff --git a/src/diskio.c b/src/diskio.c index 3044a41..4635a15 100644 --- a/src/diskio.c +++ b/src/diskio.c @@ -8,7 +8,7 @@ #include "diskio.h" /*-----------------------------------------------------------------------*/ -/* Correspondence between drive number and physical drive */ +/* Correspondence between physical drive number and physical drive. */ /* Note that Tiny-FatFs supports only single drive and always */ /* accesses drive number 0. */ @@ -92,8 +92,8 @@ DSTATUS disk_status ( DRESULT disk_read ( BYTE drv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ - DWORD sector, /* Sector number (LBA) */ - BYTE count /* Sector count (1..255) */ + DWORD sector, /* Sector address (LBA) */ + BYTE count /* Number of sectors to read (1..255) */ ) { DRESULT res; @@ -130,8 +130,8 @@ DRESULT disk_read ( DRESULT disk_write ( BYTE drv, /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ - DWORD sector, /* Sector number (LBA) */ - BYTE count /* Sector count (1..255) */ + DWORD sector, /* Sector address (LBA) */ + BYTE count /* Number of sectors to write (1..255) */ ) { DRESULT res; diff --git a/src/diskio.h b/src/diskio.h index f28c9be..d02178d 100644 --- a/src/diskio.h +++ b/src/diskio.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------- -/ Low level disk interface modlue include file R0.05 (C)ChaN, 2007 +/ Low level disk interface modlue include file R0.05x (C)ChaN, 2007 /-----------------------------------------------------------------------*/ #ifndef _DISKIO diff --git a/src/ff.c b/src/ff.c index 7c1b2c0..2ca7adc 100644 --- a/src/ff.c +++ b/src/ff.c @@ -1,11 +1,11 @@ /*--------------------------------------------------------------------------/ -/ FatFs - FAT file system module R0.05 (C)ChaN, 2007 +/ FatFs - FAT file system module R0.05a (C)ChaN, 2008 /---------------------------------------------------------------------------/ / The FatFs module is an experimenal project to implement FAT file system to / cheap microcontrollers. This is a free software and is opened for education, / research and development under license policy of following trems. / -/ Copyright (C) 2007, ChaN, all right reserved. +/ Copyright (C) 2008, ChaN, all right reserved. / / * The FatFs module is a free software and there is no warranty. / * You can use, modify and/or redistribute it for personal, non-profit or @@ -14,15 +14,19 @@ / /---------------------------------------------------------------------------/ / Feb 26, 2006 R0.00 Prototype. +/ / Apr 29, 2006 R0.01 First stable version. +/ / Jun 01, 2006 R0.02 Added FAT12 support. / Removed unbuffered mode. / Fixed a problem on small (<32M) patition. / Jun 10, 2006 R0.02a Added a configuration option (_FS_MINIMUM). +/ / Sep 22, 2006 R0.03 Added f_rename(). / Changed option _FS_MINIMUM to _FS_MINIMIZE. / Dec 11, 2006 R0.03a Improved cluster scan algolithm to write files fast. / Fixed f_mkdir() creates incorrect directory on FAT32. +/ / Feb 04, 2007 R0.04 Supported multiple drive system. / Changed some interfaces for multiple drive system. / Changed f_mountdrv() to f_mount(). @@ -35,9 +39,15 @@ / Added FSInfo support. / Fixed DBCS name can result FR_INVALID_NAME. / Fixed short seek (<= csize) collapses the file object. +/ / Aug 25, 2007 R0.05 Changed arguments of f_read(), f_write() and f_mkfs(). / Fixed f_mkfs() on FAT32 creates incorrect FSInfo. / Fixed f_mkdir() on FAT32 creates incorrect directory. +/ Feb 03, 2008 R0.05a Added f_truncate(). +/ Added f_utime(). +/ Fixed off by one error at FAT sub-type determination. +/ Fixed btr in f_read() can be mistruncated. +/ Fixed cached sector is not flushed when create and close without write. /---------------------------------------------------------------------------*/ #include @@ -63,10 +73,10 @@ WORD fsid; /* File system mount ID */ /*-----------------------------------------------------------------------*/ static -BOOL move_window ( /* TRUE: successful, FALSE: failed */ - FATFS *fs, /* File system object */ - DWORD sector /* Sector number to make apperance in the fs->win[] */ -) /* Move to zero only writes back dirty window */ +BOOL move_window ( /* TRUE: successful, FALSE: failed */ + FATFS *fs, /* File system object */ + DWORD sector /* Sector number to make apperance in the fs->win[] */ +) /* Move to zero only writes back dirty window */ { DWORD wsect; @@ -105,8 +115,8 @@ BOOL move_window ( /* TRUE: successful, FALSE: failed */ #if !_FS_READONLY static -FRESULT sync ( /* FR_OK: successful, FR_RW_ERROR: failed */ - FATFS *fs /* File system object */ +FRESULT sync ( /* FR_OK: successful, FR_RW_ERROR: failed */ + FATFS *fs /* File system object */ ) { fs->winflag = 1; @@ -126,7 +136,8 @@ FRESULT sync ( /* FR_OK: successful, FR_RW_ERROR: failed */ } #endif /* Make sure that no pending write process in the physical drive */ - if (disk_ioctl(fs->drive, CTRL_SYNC, NULL) != RES_OK) return FR_RW_ERROR; + if (disk_ioctl(fs->drive, CTRL_SYNC, NULL) != RES_OK) + return FR_RW_ERROR; return FR_OK; } #endif @@ -139,37 +150,37 @@ FRESULT sync ( /* FR_OK: successful, FR_RW_ERROR: failed */ /*-----------------------------------------------------------------------*/ static -DWORD get_cluster ( /* 0,>=2: successful, 1: failed */ - FATFS *fs, /* File system object */ - DWORD clust /* Cluster# to get the link information */ +DWORD get_cluster ( /* 0,>=2: successful, 1: failed */ + FATFS *fs, /* File system object */ + DWORD clust /* Cluster# to get the link information */ ) { WORD wc, bc; DWORD fatsect; - if (clust >= 2 && clust < fs->max_clust) { /* Valid cluster# */ + if (clust >= 2 && clust < fs->max_clust) { /* Is it a valid cluster#? */ fatsect = fs->fatbase; switch (fs->fs_type) { case FS_FAT12 : bc = (WORD)clust * 3 / 2; - if (!move_window(fs, fatsect + (bc / S_SIZ))) break; - wc = fs->win[bc & (S_SIZ - 1)]; bc++; - if (!move_window(fs, fatsect + (bc / S_SIZ))) break; - wc |= (WORD)fs->win[bc & (S_SIZ - 1)] << 8; + if (!move_window(fs, fatsect + (bc / SS(fs)))) break; + wc = fs->win[bc & (SS(fs) - 1)]; bc++; + if (!move_window(fs, fatsect + (bc / SS(fs)))) break; + wc |= (WORD)fs->win[bc & (SS(fs) - 1)] << 8; return (clust & 1) ? (wc >> 4) : (wc & 0xFFF); case FS_FAT16 : - if (!move_window(fs, fatsect + (clust / (S_SIZ / 2)))) break; - return LD_WORD(&fs->win[((WORD)clust * 2) & (S_SIZ - 1)]); + if (!move_window(fs, fatsect + (clust / (SS(fs) / 2)))) break; + return LD_WORD(&fs->win[((WORD)clust * 2) & (SS(fs) - 1)]); case FS_FAT32 : - if (!move_window(fs, fatsect + (clust / (S_SIZ / 4)))) break; - return LD_DWORD(&fs->win[((WORD)clust * 4) & (S_SIZ - 1)]) & 0x0FFFFFFF; + if (!move_window(fs, fatsect + (clust / (SS(fs) / 4)))) break; + return LD_DWORD(&fs->win[((WORD)clust * 4) & (SS(fs) - 1)]) & 0x0FFFFFFF; } } - return 1; /* There is no cluster information, or an error occured */ + return 1; /* Out of cluster range, or an error occured */ } @@ -181,10 +192,10 @@ DWORD get_cluster ( /* 0,>=2: successful, 1: failed */ #if !_FS_READONLY static -BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ - FATFS *fs, /* File system object */ - DWORD clust, /* Cluster# to change */ - DWORD val /* New value to mark the cluster */ +BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ + FATFS *fs, /* File system object */ + DWORD clust, /* Cluster# to change (must be 2 to fs->max_clust-1) */ + DWORD val /* New value to mark the cluster */ ) { WORD bc; @@ -196,24 +207,24 @@ BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ switch (fs->fs_type) { case FS_FAT12 : bc = (WORD)clust * 3 / 2; - if (!move_window(fs, fatsect + (bc / S_SIZ))) return FALSE; - p = &fs->win[bc & (S_SIZ - 1)]; + if (!move_window(fs, fatsect + (bc / SS(fs)))) return FALSE; + p = &fs->win[bc & (SS(fs) - 1)]; *p = (clust & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; bc++; fs->winflag = 1; - if (!move_window(fs, fatsect + (bc / S_SIZ))) return FALSE; - p = &fs->win[bc & (S_SIZ - 1)]; + if (!move_window(fs, fatsect + (bc / SS(fs)))) return FALSE; + p = &fs->win[bc & (SS(fs) - 1)]; *p = (clust & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); break; case FS_FAT16 : - if (!move_window(fs, fatsect + (clust / (S_SIZ / 2)))) return FALSE; - ST_WORD(&fs->win[((WORD)clust * 2) & (S_SIZ - 1)], (WORD)val); + if (!move_window(fs, fatsect + (clust / (SS(fs) / 2)))) return FALSE; + ST_WORD(&fs->win[((WORD)clust * 2) & (SS(fs) - 1)], (WORD)val); break; case FS_FAT32 : - if (!move_window(fs, fatsect + (clust / (S_SIZ / 4)))) return FALSE; - ST_DWORD(&fs->win[((WORD)clust * 4) & (S_SIZ - 1)], val); + if (!move_window(fs, fatsect + (clust / (SS(fs) / 4)))) return FALSE; + ST_DWORD(&fs->win[((WORD)clust * 4) & (SS(fs) - 1)], val); break; default : @@ -233,9 +244,9 @@ BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ #if !_FS_READONLY static -BOOL remove_chain ( /* TRUE: successful, FALSE: failed */ - FATFS *fs, /* File system object */ - DWORD clust /* Cluster# to remove chain from */ +BOOL remove_chain ( /* TRUE: successful, FALSE: failed */ + FATFS *fs, /* File system object */ + DWORD clust /* Cluster# to remove chain from */ ) { DWORD nxt; @@ -266,7 +277,7 @@ BOOL remove_chain ( /* TRUE: successful, FALSE: failed */ #if !_FS_READONLY static -DWORD create_chain ( /* 0: no free cluster, 1: error, >=2: new cluster number */ +DWORD create_chain ( /* 0: No free cluster, 1: Error, >=2: New cluster number */ FATFS *fs, /* File system object */ DWORD clust /* Cluster# to stretch, 0 means create new */ ) @@ -340,30 +351,29 @@ DWORD clust2sect ( /* !=0: sector number, 0: failed - invalid cluster# */ static BOOL next_dir_entry ( /* TRUE: successful, FALSE: could not move next */ - DIR *dirobj /* Pointer to directory object */ + DIR *dj /* Pointer to directory object */ ) { DWORD clust; WORD idx; - FATFS *fs = dirobj->fs; - idx = dirobj->index + 1; - if ((idx & ((S_SIZ - 1) / 32)) == 0) { /* Table sector changed? */ - dirobj->sect++; /* Next sector */ - if (!dirobj->clust) { /* In static table */ - if (idx >= fs->n_rootdir) return FALSE; /* Reached to end of table */ + idx = dj->index + 1; + if ((idx & ((SS(dj->fs) - 1) / 32)) == 0) { /* Table sector changed? */ + dj->sect++; /* Next sector */ + if (!dj->clust) { /* In static table */ + if (idx >= dj->fs->n_rootdir) return FALSE; /* Reached to end of table */ } else { /* In dynamic table */ - if (((idx / (S_SIZ / 32)) & (fs->sects_clust - 1)) == 0) { /* Cluster changed? */ - clust = get_cluster(fs, dirobj->clust); /* Get next cluster */ - if (clust < 2 || clust >= fs->max_clust) /* Reached to end of table */ + if (((idx / (SS(dj->fs) / 32)) & (dj->fs->sects_clust - 1)) == 0) { /* Cluster changed? */ + clust = get_cluster(dj->fs, dj->clust); /* Get next cluster */ + if (clust < 2 || clust >= dj->fs->max_clust) /* Reached to end of table */ return FALSE; - dirobj->clust = clust; /* Initialize for new cluster */ - dirobj->sect = clust2sect(fs, clust); + dj->clust = clust; /* Initialize for new cluster */ + dj->sect = clust2sect(dj->fs, clust); } } } - dirobj->index = idx; /* Lower 4 bit of dirobj->index indicates offset in dirobj->sect */ + dj->index = idx; /* Lower several bits of dj->index indicates offset in dj->sect */ return TRUE; } @@ -376,9 +386,9 @@ BOOL next_dir_entry ( /* TRUE: successful, FALSE: could not move next */ #if _FS_MINIMIZE <= 1 static -void get_fileinfo ( /* No return code */ - FILINFO *finfo, /* Ptr to store the file information */ - const BYTE *dir /* Ptr to the directory entry */ +void get_fileinfo ( /* No return code */ + FILINFO *finfo, /* Ptr to store the file information */ + const BYTE *dir /* Ptr to the directory entry */ ) { BYTE n, c, a; @@ -420,9 +430,9 @@ void get_fileinfo ( /* No return code */ /*-----------------------------------------------------------------------*/ static -char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next character */ - const char **path, /* Pointer to the file path pointer */ - char *dirname /* Pointer to directory name buffer {Name(8), Ext(3), NT flag(1)} */ +char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next character */ + const char **path, /* Pointer to the file path pointer */ + char *dirname /* Pointer to directory name buffer {Name(8), Ext(3), NT flag(1)} */ ) { BYTE n, t, c, a, b; @@ -450,7 +460,7 @@ char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next (c >= 0xE0 && c <= 0xFC))) { if (n == 0 && c == 0xE5) /* Change heading \xE5 to \x05 */ c = 0x05; - a ^= 1; goto md_l2; + a ^= 0x01; goto md_l2; } if (c == '"') break; /* Reject " */ if (c <= ')') goto md_l1; /* Accept ! # $ % & ' ( ) */ @@ -461,14 +471,14 @@ char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next if (c == '|') break; /* Reject | */ if (c >= '[' && c <= ']') break;/* Reject [ \ ] */ if (_USE_NTFLAG && c >= 'A' && c <= 'Z') - (t == 8) ? (b &= ~0x08) : (b &= ~0x10); + (t == 8) ? (b &= 0xF7) : (b &= 0xEF); if (c >= 'a' && c <= 'z') { /* Convert to upper case */ c -= 0x20; if (_USE_NTFLAG) (t == 8) ? (a |= 0x08) : (a |= 0x10); } } md_l1: - a &= ~1; + a &= 0xFE; md_l2: if (n >= t) break; dirname[n++] = c; @@ -485,28 +495,28 @@ char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next static FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ - DIR *dirobj, /* Pointer to directory object to return last directory */ + DIR *dj, /* Pointer to directory object to return last directory */ char *fn, /* Pointer to last segment name to return {file(8),ext(3),attr(1)} */ const char *path, /* Full-path string to trace a file or directory */ - BYTE **dir /* Directory pointer in Win[] to retutn */ + BYTE **dir /* Pointer to pointer to found entry to retutn */ ) { DWORD clust; char ds; BYTE *dptr = NULL; - FATFS *fs = dirobj->fs; /* Get logical drive from the given DIR structure */ + FATFS *fs = dj->fs; /* Initialize directory object */ clust = fs->dirbase; if (fs->fs_type == FS_FAT32) { - dirobj->clust = dirobj->sclust = clust; - dirobj->sect = clust2sect(fs, clust); + dj->clust = dj->sclust = clust; + dj->sect = clust2sect(fs, clust); } else { - dirobj->clust = dirobj->sclust = 0; - dirobj->sect = clust; + dj->clust = dj->sclust = 0; + dj->sect = clust; } - dirobj->index = 0; + dj->index = 0; if (*path == '\0') { /* Null path means the root directory */ *dir = NULL; return FR_OK; @@ -516,22 +526,22 @@ FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ ds = make_dirfile(&path, fn); /* Get a paragraph into fn[] */ if (ds == 1) return FR_INVALID_NAME; for (;;) { - if (!move_window(fs, dirobj->sect)) return FR_RW_ERROR; - dptr = &fs->win[(dirobj->index & ((S_SIZ - 1) / 32)) * 32]; /* Pointer to the directory entry */ + if (!move_window(fs, dj->sect)) return FR_RW_ERROR; + dptr = &fs->win[(dj->index & ((SS(fs) - 1) / 32)) * 32]; /* Pointer to the directory entry */ if (dptr[DIR_Name] == 0) /* Has it reached to end of dir? */ return !ds ? FR_NO_FILE : FR_NO_PATH; if (dptr[DIR_Name] != 0xE5 /* Matched? */ && !(dptr[DIR_Attr] & AM_VOL) && !memcmp(&dptr[DIR_Name], fn, 8+3) ) break; - if (!next_dir_entry(dirobj)) /* Next directory pointer */ + if (!next_dir_entry(dj)) /* Next directory pointer */ return !ds ? FR_NO_FILE : FR_NO_PATH; } if (!ds) { *dir = dptr; return FR_OK; } /* Matched with end of path */ if (!(dptr[DIR_Attr] & AM_DIR)) return FR_NO_PATH; /* Cannot trace because it is a file */ clust = ((DWORD)LD_WORD(&dptr[DIR_FstClusHI]) << 16) | LD_WORD(&dptr[DIR_FstClusLO]); /* Get cluster# of the directory */ - dirobj->clust = dirobj->sclust = clust; /* Restart scanning at the new directory */ - dirobj->sect = clust2sect(fs, clust); - dirobj->index = 2; + dj->clust = dj->sclust = clust; /* Restart scanning at the new directory */ + dj->sect = clust2sect(fs, clust); + dj->index = 2; } } @@ -545,41 +555,42 @@ FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ #if !_FS_READONLY static FRESULT reserve_direntry ( /* FR_OK: successful, FR_DENIED: no free entry, FR_RW_ERROR: a disk error occured */ - DIR *dirobj, /* Target directory to create new entry */ + DIR *dj, /* Target directory to create new entry */ BYTE **dir /* Pointer to pointer to created entry to retutn */ ) { DWORD clust, sector; BYTE c, n, *dptr; - FATFS *fs = dirobj->fs; + FATFS *fs = dj->fs; /* Re-initialize directory object */ - clust = dirobj->sclust; + clust = dj->sclust; if (clust) { /* Dyanmic directory table */ - dirobj->clust = clust; - dirobj->sect = clust2sect(fs, clust); + dj->clust = clust; + dj->sect = clust2sect(fs, clust); } else { /* Static directory table */ - dirobj->sect = fs->dirbase; + dj->sect = fs->dirbase; } - dirobj->index = 0; + dj->index = 0; do { - if (!move_window(fs, dirobj->sect)) return FR_RW_ERROR; - dptr = &fs->win[(dirobj->index & ((S_SIZ - 1) / 32)) * 32]; /* Pointer to the directory entry */ + if (!move_window(fs, dj->sect)) return FR_RW_ERROR; + dptr = &fs->win[(dj->index & ((SS(dj->fs) - 1) / 32)) * 32]; /* Pointer to the directory entry */ c = dptr[DIR_Name]; - if (c == 0 || c == 0xE5) { /* Found an empty entry! */ + if (c == 0 || c == 0xE5) { /* Found an empty entry */ *dir = dptr; return FR_OK; } - } while (next_dir_entry(dirobj)); /* Next directory pointer */ + } while (next_dir_entry(dj)); /* Next directory pointer */ /* Reached to end of the directory table */ - /* Abort when static table or could not stretch dynamic table */ - if (!clust || !(clust = create_chain(fs, dirobj->clust))) return FR_DENIED; + /* Abort when it is a static table or could not stretch dynamic table */ + if (!clust || !(clust = create_chain(fs, dj->clust))) return FR_DENIED; if (clust == 1 || !move_window(fs, 0)) return FR_RW_ERROR; - fs->winsect = sector = clust2sect(fs, clust); /* Cleanup the expanded table */ - memset(fs->win, 0, S_SIZ); + /* Cleanup the expanded table */ + fs->winsect = sector = clust2sect(fs, clust); + memset(fs->win, 0, SS(fs)); for (n = fs->sects_clust; n; n--) { if (disk_write(fs->drive, fs->win, sector, 1) != RES_OK) return FR_RW_ERROR; @@ -587,6 +598,7 @@ FRESULT reserve_direntry ( /* FR_OK: successful, FR_DENIED: no free entry, FR_RW } fs->winflag = 1; *dir = fs->win; + return FR_OK; } #endif /* !_FS_READONLY */ @@ -595,13 +607,13 @@ FRESULT reserve_direntry ( /* FR_OK: successful, FR_DENIED: no free entry, FR_RW /*-----------------------------------------------------------------------*/ -/* Load boot record and check if it is a FAT boot record */ +/* Load boot record and check if it is an FAT boot record */ /*-----------------------------------------------------------------------*/ static -BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2:Not a boot record or error */ - FATFS *fs, /* File system object */ - DWORD sect /* Sector# (lba) to check if it is a FAT boot record or not */ +BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2:Not a boot record or error */ + FATFS *fs, /* File system object */ + DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */ ) { if (disk_read(fs->drive, fs->win, sect, 1) != RES_OK) /* Load boot record */ @@ -625,10 +637,10 @@ BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2 /*-----------------------------------------------------------------------*/ static -FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ - const char **path, /* Pointer to pointer to the path name (drive number) */ - FATFS **rfs, /* Pointer to pointer to the found file system object */ - BYTE chk_wp /* !=0: Check media write protection for write access */ +FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ + const char **path, /* Pointer to pointer to the path name (drive number) */ + FATFS **rfs, /* Pointer to pointer to the found file system object */ + BYTE chk_wp /* !=0: Check media write protection for write access */ ) { BYTE drv, fmt, *tbl; @@ -642,38 +654,37 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ while (*p == ' ') p++; /* Strip leading spaces */ drv = p[0] - '0'; /* Is there a drive number? */ if (drv <= 9 && p[1] == ':') - p += 2; /* Found a drive number, get and strip it */ + p += 2; /* Found a drive number, get and strip it */ else - drv = 0; /* No drive number is given, use drive number 0 as default */ - if (*p == '/') p++; /* Strip heading slash */ - *path = p; /* Return pointer to the path name */ + drv = 0; /* No drive number is given, use drive number 0 as default */ + if (*p == '/') p++; /* Strip heading slash */ + *path = p; /* Return pointer to the path name */ /* Check if the drive number is valid or not */ if (drv >= _DRIVES) return FR_INVALID_DRIVE; /* Is the drive number valid? */ - if (!(fs = FatFs[drv])) return FR_NOT_ENABLED; /* Is the file system object registered? */ - *rfs = fs; /* Returen pointer to the corresponding file system object */ + *rfs = fs = FatFs[drv]; /* Returen pointer to the corresponding file system object */ + if (!fs) return FR_NOT_ENABLED; /* Is the file system object registered? */ - /* Check if the logical drive has been mounted or not */ - if (fs->fs_type) { + if (fs->fs_type) { /* If the logical drive has been mounted */ stat = disk_status(fs->drive); - if (!(stat & STA_NOINIT)) { /* If the physical drive is kept initialized */ + if (!(stat & STA_NOINIT)) { /* and physical drive is kept initialized (has not been changed), */ #if !_FS_READONLY if (chk_wp && (stat & STA_PROTECT)) /* Check write protection if needed */ return FR_WRITE_PROTECTED; #endif - return FR_OK; /* The file system object is valid */ + return FR_OK; /* The file system object is valid */ } } - /* The logical drive has not been mounted, following code attempts to mount the logical drive */ + /* The logical drive must be re-mounted. Following code attempts to mount the logical drive */ memset(fs, 0, sizeof(FATFS)); /* Clean-up the file system object */ fs->drive = LD2PD(drv); /* Bind the logical drive and a physical drive */ stat = disk_initialize(fs->drive); /* Initialize low level disk I/O layer */ if (stat & STA_NOINIT) /* Check if the drive is ready */ return FR_NOT_READY; -#if S_MAX_SIZ > 512 /* Check disk sector size */ - if (disk_ioctl(drv, GET_SECTOR_SIZE, &S_SIZ) != RES_OK || S_SIZ > S_MAX_SIZ) +#if S_MAX_SIZ > 512 /* Get disk sector size if needed */ + if (disk_ioctl(drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK || SS(fs) > S_MAX_SIZ) return FR_NO_FILESYSTEM; #endif #if !_FS_READONLY @@ -682,15 +693,15 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ #endif /* Search FAT partition on the drive */ fmt = check_fs(fs, bootsect = 0); /* Check sector 0 as an SFD format */ - if (fmt == 1) { /* Not a FAT boot record, it may be patitioned */ + if (fmt == 1) { /* Not an FAT boot record, it may be patitioned */ /* Check a partition listed in top of the partition table */ tbl = &fs->win[MBR_Table + LD2PT(drv) * 16]; /* Partition table */ - if (tbl[4]) { /* Is the partition existing? */ - bootsect = LD_DWORD(&tbl[8]); /* Partition offset in LBA */ - fmt = check_fs(fs, bootsect); /* Check the partition */ + if (tbl[4]) { /* Is the partition existing? */ + bootsect = LD_DWORD(&tbl[8]); /* Partition offset in LBA */ + fmt = check_fs(fs, bootsect); /* Check the partition */ } } - if (fmt || LD_WORD(&fs->win[BPB_BytsPerSec]) != S_SIZ) /* No valid FAT patition is found */ + if (fmt || LD_WORD(&fs->win[BPB_BytsPerSec]) != SS(fs)) /* No valid FAT patition is found */ return FR_NO_FILESYSTEM; /* Initialize the file system object */ @@ -704,25 +715,25 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ fs->n_rootdir = LD_WORD(&fs->win[BPB_RootEntCnt]); /* Nmuber of root directory entries */ totalsect = LD_WORD(&fs->win[BPB_TotSec16]); /* Number of sectors on the file system */ if (!totalsect) totalsect = LD_DWORD(&fs->win[BPB_TotSec32]); - fs->max_clust = maxclust = (totalsect /* Last cluster# + 1 */ - - LD_WORD(&fs->win[BPB_RsvdSecCnt]) - fatsize - fs->n_rootdir / (S_SIZ/32) + fs->max_clust = maxclust = (totalsect /* max_clust = Last cluster# + 1 */ + - LD_WORD(&fs->win[BPB_RsvdSecCnt]) - fatsize - fs->n_rootdir / (SS(fs)/32) ) / fs->sects_clust + 2; fmt = FS_FAT12; /* Determine the FAT sub type */ - if (maxclust > 0xFF7) fmt = FS_FAT16; - if (maxclust > 0xFFF7) fmt = FS_FAT32; - fs->fs_type = fmt; + if (maxclust >= 0xFF7) fmt = FS_FAT16; + if (maxclust >= 0xFFF7) fmt = FS_FAT32; if (fmt == FS_FAT32) fs->dirbase = LD_DWORD(&fs->win[BPB_RootClus]); /* Root directory start cluster */ else fs->dirbase = fs->fatbase + fatsize; /* Root directory start sector (lba) */ - fs->database = fs->fatbase + fatsize + fs->n_rootdir / (S_SIZ/32); /* Data start sector (lba) */ + fs->database = fs->fatbase + fatsize + fs->n_rootdir / (SS(fs)/32); /* Data start sector (lba) */ #if !_FS_READONLY + /* Initialize allocation information */ fs->free_clust = 0xFFFFFFFF; #if _USE_FSINFO - /* Load fsinfo sector if needed */ + /* Get fsinfo if needed */ if (fmt == FS_FAT32) { fs->fsi_sector = bootsect + LD_WORD(&fs->win[BPB_FSInfo]); if (disk_read(fs->drive, fs->win, fs->fsi_sector, 1) == RES_OK && @@ -735,7 +746,9 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ } #endif #endif - fs->id = ++fsid; /* File system mount ID */ + + fs->fs_type = fmt; /* FAT syb-type */ + fs->id = ++fsid; /* File system mount ID */ return FR_OK; } @@ -747,12 +760,12 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ /*-----------------------------------------------------------------------*/ static -FRESULT validate ( /* FR_OK(0): The object is valid, !=0: Not valid */ +FRESULT validate ( /* FR_OK(0): The object is valid, !=0: Invalid */ const FATFS *fs, /* Pointer to the file system object */ - WORD id /* id member of the target object to be checked */ + WORD id /* Member id of the target object to be checked */ ) { - if (!fs || fs->id != id) + if (!fs || !fs->fs_type || fs->id != id) return FR_INVALID_OBJECT; if (disk_status(fs->drive) & STA_NOINIT) return FR_NOT_READY; @@ -780,14 +793,12 @@ FRESULT f_mount ( FATFS *fs /* Pointer to new file system object (NULL for unmount)*/ ) { - FATFS *fsobj; - - if (drv >= _DRIVES) return FR_INVALID_DRIVE; - fsobj = FatFs[drv]; - FatFs[drv] = fs; - if (fsobj) memset(fsobj, 0, sizeof(FATFS)); - if (fs) memset(fs, 0, sizeof(FATFS)); + + if (FatFs[drv]) FatFs[drv]->fs_type = 0; /* Clear old object */ + + FatFs[drv] = fs; /* Register and clear new object */ + if (fs) fs->fs_type = 0; return FR_OK; } @@ -806,34 +817,31 @@ FRESULT f_open ( ) { FRESULT res; + DIR dj; BYTE *dir; - DIR dirobj; char fn[8+3+1]; - FATFS *fs; fp->fs = NULL; #if !_FS_READONLY mode &= (FA_READ|FA_WRITE|FA_CREATE_ALWAYS|FA_OPEN_ALWAYS|FA_CREATE_NEW); - res = auto_mount(&path, &fs, (BYTE)(mode & (FA_WRITE|FA_CREATE_ALWAYS|FA_OPEN_ALWAYS|FA_CREATE_NEW))); + res = auto_mount(&path, &dj.fs, (BYTE)(mode & (FA_WRITE|FA_CREATE_ALWAYS|FA_OPEN_ALWAYS|FA_CREATE_NEW))); #else mode &= FA_READ; - res = auto_mount(&path, &fs, 0); + res = auto_mount(&path, &dj.fs, 0); #endif if (res != FR_OK) return res; - dirobj.fs = fs; + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ - /* Trace the file path */ - res = trace_path(&dirobj, fn, path, &dir); #if !_FS_READONLY /* Create or Open a file */ if (mode & (FA_CREATE_ALWAYS|FA_OPEN_ALWAYS|FA_CREATE_NEW)) { DWORD ps, rs; if (res != FR_OK) { /* No file, create new */ if (res != FR_NO_FILE) return res; - res = reserve_direntry(&dirobj, &dir); + res = reserve_direntry(&dj, &dir); if (res != FR_OK) return res; - memset(dir, 0, 32); /* Initialize the new entry with open name */ + memset(dir, 0, 32); /* Initialize the new entry with open name */ memcpy(&dir[DIR_Name], fn, 8+3); dir[DIR_NTres] = fn[11]; mode |= FA_CREATE_ALWAYS; @@ -841,49 +849,49 @@ FRESULT f_open ( else { /* Any object is already existing */ if (mode & FA_CREATE_NEW) /* Cannot create new */ return FR_EXIST; - if (dir == NULL || (dir[DIR_Attr] & (AM_RDO|AM_DIR))) /* Cannot overwrite it (R/O or DIR) */ + if (!dir || (dir[DIR_Attr] & (AM_RDO|AM_DIR))) /* Cannot overwrite it (R/O or DIR) */ return FR_DENIED; if (mode & FA_CREATE_ALWAYS) { /* Resize it to zero if needed */ rs = ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | LD_WORD(&dir[DIR_FstClusLO]); /* Get start cluster */ ST_WORD(&dir[DIR_FstClusHI], 0); /* cluster = 0 */ ST_WORD(&dir[DIR_FstClusLO], 0); ST_DWORD(&dir[DIR_FileSize], 0); /* size = 0 */ - fs->winflag = 1; - ps = fs->winsect; /* Remove the cluster chain */ - if (!remove_chain(fs, rs) || !move_window(fs, ps)) + dj.fs->winflag = 1; + ps = dj.fs->winsect; /* Remove the cluster chain */ + if (!remove_chain(dj.fs, rs) || !move_window(dj.fs, ps)) return FR_RW_ERROR; - fs->last_clust = rs - 1; /* Reuse the cluster hole */ + dj.fs->last_clust = rs - 1; /* Reuse the cluster hole */ } } if (mode & FA_CREATE_ALWAYS) { - dir[DIR_Attr] = AM_ARC; /* New attribute */ + dir[DIR_Attr] = 0; /* Reset attribute */ ps = get_fattime(); - ST_DWORD(&dir[DIR_WrtTime], ps); /* Updated time */ ST_DWORD(&dir[DIR_CrtTime], ps); /* Created time */ - fs->winflag = 1; + dj.fs->winflag = 1; + mode |= FA__WRITTEN; /* Set file changed flag */ } } /* Open an existing file */ else { #endif /* !_FS_READONLY */ - if (res != FR_OK) return res; /* Trace failed */ - if (dir == NULL || (dir[DIR_Attr] & AM_DIR)) /* It is a directory */ + if (res != FR_OK) return res; /* Trace failed */ + if (!dir || (dir[DIR_Attr] & AM_DIR)) /* It is a directory */ return FR_NO_FILE; #if !_FS_READONLY if ((mode & FA_WRITE) && (dir[DIR_Attr] & AM_RDO)) /* R/O violation */ return FR_DENIED; } - fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ + fp->dir_sect = dj.fs->winsect; /* Pointer to the directory entry */ fp->dir_ptr = dir; #endif fp->flag = mode; /* File access mode */ fp->org_clust = /* File start cluster */ ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | LD_WORD(&dir[DIR_FstClusLO]); fp->fsize = LD_DWORD(&dir[DIR_FileSize]); /* File size */ - fp->fptr = 0; /* File ptr */ - fp->sect_clust = 1; /* Sector counter */ - fp->fs = fs; fp->id = fs->id; /* Owner file system object of the file */ + fp->fptr = 0; /* Initialze file pointer */ + fp->sect_clust = 1; /* Initialize sector counter */ + fp->fs = dj.fs; fp->id = dj.fs->id; /* Owner file system object of the file */ return FR_OK; } @@ -902,59 +910,58 @@ FRESULT f_read ( UINT *br /* Pointer to number of bytes read */ ) { + FRESULT res; DWORD clust, sect, remain; UINT rcnt, cc; BYTE *rbuff = buff; - FRESULT res; - FATFS *fs = fp->fs; *br = 0; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res != FR_OK) return res; if (fp->flag & FA__ERROR) return FR_RW_ERROR; /* Check error flag */ if (!(fp->flag & FA_READ)) return FR_DENIED; /* Check access mode */ remain = fp->fsize - fp->fptr; - if (btr > remain) btr = (WORD)remain; /* Truncate read count by number of bytes left */ + if (btr > remain) btr = (UINT)remain; /* Truncate read count by number of bytes left */ for ( ; btr; /* Repeat until all data transferred */ rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { - if ((fp->fptr & (S_SIZ - 1)) == 0) { /* On the sector boundary */ + if ((fp->fptr & (SS(fp->fs) - 1)) == 0) { /* On the sector boundary */ if (--fp->sect_clust) { /* Decrement left sector counter */ sect = fp->curr_sect + 1; /* Get current sector */ } else { /* On the cluster boundary, get next cluster */ clust = (fp->fptr == 0) ? - fp->org_clust : get_cluster(fs, fp->curr_clust); - if (clust < 2 || clust >= fs->max_clust) + fp->org_clust : get_cluster(fp->fs, fp->curr_clust); + if (clust < 2 || clust >= fp->fs->max_clust) goto fr_error; fp->curr_clust = clust; /* Current cluster */ - sect = clust2sect(fs, clust); /* Get current sector */ - fp->sect_clust = fs->sects_clust; /* Re-initialize the left sector counter */ + sect = clust2sect(fp->fs, clust); /* Get current sector */ + fp->sect_clust = fp->fs->sects_clust; /* Re-initialize the left sector counter */ } #if !_FS_READONLY if (fp->flag & FA__DIRTY) { /* Flush file I/O buffer if needed */ - if (disk_write(fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) + if (disk_write(fp->fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) goto fr_error; - fp->flag &= ~FA__DIRTY; + fp->flag &= (BYTE)~FA__DIRTY; } #endif fp->curr_sect = sect; /* Update current sector */ - cc = btr / S_SIZ; /* When left bytes >= S_SIZ, */ + cc = btr / SS(fp->fs); /* When left bytes >= SS, */ if (cc) { /* Read maximum contiguous sectors directly */ if (cc > fp->sect_clust) cc = fp->sect_clust; - if (disk_read(fs->drive, rbuff, sect, (BYTE)cc) != RES_OK) + if (disk_read(fp->fs->drive, rbuff, sect, (BYTE)cc) != RES_OK) goto fr_error; fp->sect_clust -= (BYTE)(cc - 1); fp->curr_sect += cc - 1; - rcnt = cc * S_SIZ; + rcnt = cc * SS(fp->fs); continue; } - if (disk_read(fs->drive, fp->buffer, sect, 1) != RES_OK) /* Load the sector into file I/O buffer */ + if (disk_read(fp->fs->drive, fp->buffer, sect, 1) != RES_OK) /* Load the sector into file I/O buffer */ goto fr_error; } - rcnt = S_SIZ - ((WORD)fp->fptr & (S_SIZ - 1)); /* Copy fractional bytes from file I/O buffer */ + rcnt = SS(fp->fs) - (fp->fptr & (SS(fp->fs) - 1)); /* Copy fractional bytes from file I/O buffer */ if (rcnt > btr) rcnt = btr; - memcpy(rbuff, &fp->buffer[fp->fptr & (S_SIZ - 1)], rcnt); + memcpy(rbuff, &fp->buffer[fp->fptr & (SS(fp->fs) - 1)], rcnt); } return FR_OK; @@ -979,15 +986,14 @@ FRESULT f_write ( UINT *bw /* Pointer to number of bytes written */ ) { + FRESULT res; DWORD clust, sect; UINT wcnt, cc; const BYTE *wbuff = buff; - FRESULT res; - FATFS *fs = fp->fs; *bw = 0; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res != FR_OK) return res; if (fp->flag & FA__ERROR) return FR_RW_ERROR; /* Check error flag */ if (!(fp->flag & FA_WRITE)) return FR_DENIED; /* Check access mode */ @@ -995,46 +1001,46 @@ FRESULT f_write ( for ( ; btw; /* Repeat until all data transferred */ wbuff += wcnt, fp->fptr += wcnt, *bw += wcnt, btw -= wcnt) { - if ((fp->fptr & (S_SIZ - 1)) == 0) { /* On the sector boundary */ + if ((fp->fptr & (SS(fp->fs) - 1)) == 0) { /* On the sector boundary */ if (--fp->sect_clust) { /* Decrement left sector counter */ sect = fp->curr_sect + 1; /* Get current sector */ } else { /* On the cluster boundary, get next cluster */ if (fp->fptr == 0) { /* Is top of the file */ clust = fp->org_clust; if (clust == 0) /* No cluster is created yet */ - fp->org_clust = clust = create_chain(fs, 0); /* Create a new cluster chain */ + fp->org_clust = clust = create_chain(fp->fs, 0); /* Create a new cluster chain */ } else { /* Middle or end of file */ - clust = create_chain(fs, fp->curr_clust); /* Trace or streach cluster chain */ + clust = create_chain(fp->fs, fp->curr_clust); /* Trace or streach cluster chain */ } if (clust == 0) break; /* Disk full */ - if (clust == 1 || clust >= fs->max_clust) goto fw_error; + if (clust == 1 || clust >= fp->fs->max_clust) goto fw_error; fp->curr_clust = clust; /* Current cluster */ - sect = clust2sect(fs, clust); /* Get current sector */ - fp->sect_clust = fs->sects_clust; /* Re-initialize the left sector counter */ + sect = clust2sect(fp->fs, clust); /* Get current sector */ + fp->sect_clust = fp->fs->sects_clust; /* Re-initialize the left sector counter */ } if (fp->flag & FA__DIRTY) { /* Flush file I/O buffer if needed */ - if (disk_write(fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) + if (disk_write(fp->fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) goto fw_error; - fp->flag &= ~FA__DIRTY; + fp->flag &= (BYTE)~FA__DIRTY; } fp->curr_sect = sect; /* Update current sector */ - cc = btw / S_SIZ; /* When left bytes >= S_SIZ, */ + cc = btw / SS(fp->fs); /* When left bytes >= SS, */ if (cc) { /* Write maximum contiguous sectors directly */ if (cc > fp->sect_clust) cc = fp->sect_clust; - if (disk_write(fs->drive, wbuff, sect, (BYTE)cc) != RES_OK) + if (disk_write(fp->fs->drive, wbuff, sect, (BYTE)cc) != RES_OK) goto fw_error; fp->sect_clust -= (BYTE)(cc - 1); fp->curr_sect += cc - 1; - wcnt = cc * S_SIZ; + wcnt = cc * SS(fp->fs); continue; } if (fp->fptr < fp->fsize && /* Fill sector buffer with file data if needed */ - disk_read(fs->drive, fp->buffer, sect, 1) != RES_OK) + disk_read(fp->fs->drive, fp->buffer, sect, 1) != RES_OK) goto fw_error; } - wcnt = S_SIZ - ((WORD)fp->fptr & (S_SIZ - 1)); /* Copy fractional bytes to file I/O buffer */ + wcnt = SS(fp->fs) - (fp->fptr & (SS(fp->fs) - 1)); /* Copy fractional bytes to file I/O buffer */ if (wcnt > btw) wcnt = btw; - memcpy(&fp->buffer[fp->fptr & (S_SIZ - 1)], wbuff, wcnt); + memcpy(&fp->buffer[fp->fptr & (SS(fp->fs) - 1)], wbuff, wcnt); fp->flag |= FA__DIRTY; } @@ -1058,23 +1064,22 @@ FRESULT f_sync ( FIL *fp /* Pointer to the file object */ ) { + FRESULT res; DWORD tim; BYTE *dir; - FRESULT res; - FATFS *fs = fp->fs; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res == FR_OK) { if (fp->flag & FA__WRITTEN) { /* Has the file been written? */ /* Write back data buffer if needed */ if (fp->flag & FA__DIRTY) { - if (disk_write(fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) + if (disk_write(fp->fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) return FR_RW_ERROR; - fp->flag &= ~FA__DIRTY; + fp->flag &= (BYTE)~FA__DIRTY; } /* Update the directory entry */ - if (!move_window(fs, fp->dir_sect)) + if (!move_window(fp->fs, fp->dir_sect)) return FR_RW_ERROR; dir = fp->dir_ptr; dir[DIR_Attr] |= AM_ARC; /* Set archive bit */ @@ -1083,8 +1088,8 @@ FRESULT f_sync ( ST_WORD(&dir[DIR_FstClusHI], fp->org_clust >> 16); tim = get_fattime(); /* Updated time */ ST_DWORD(&dir[DIR_WrtTime], tim); - fp->flag &= ~FA__WRITTEN; - res = sync(fs); + fp->flag &= (BYTE)~FA__WRITTEN; + res = sync(fp->fs); } } return res; @@ -1111,8 +1116,7 @@ FRESULT f_close ( #else res = validate(fp->fs, fp->id); #endif - if (res == FR_OK) - fp->fs = NULL; + if (res == FR_OK) fp->fs = NULL; return res; } @@ -1129,20 +1133,19 @@ FRESULT f_lseek ( DWORD ofs /* File pointer from top of file */ ) { + FRESULT res; DWORD clust, csize; BYTE csect; - FRESULT res; - FATFS *fs = fp->fs; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res != FR_OK) return res; if (fp->flag & FA__ERROR) return FR_RW_ERROR; #if !_FS_READONLY if (fp->flag & FA__DIRTY) { /* Write-back dirty buffer if needed */ - if (disk_write(fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) + if (disk_write(fp->fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) goto fk_error; - fp->flag &= ~FA__DIRTY; + fp->flag &= (BYTE)~FA__DIRTY; } if (ofs > fp->fsize && !(fp->flag & FA_WRITE)) #else @@ -1156,36 +1159,36 @@ FRESULT f_lseek ( clust = fp->org_clust; /* Get start cluster */ #if !_FS_READONLY if (!clust) { /* If the file does not have a cluster chain, create new cluster chain */ - clust = create_chain(fs, 0); + clust = create_chain(fp->fs, 0); if (clust == 1) goto fk_error; fp->org_clust = clust; } #endif if (clust) { /* If the file has a cluster chain, it can be followed */ - csize = (DWORD)fs->sects_clust * S_SIZ; /* Cluster size in unit of byte */ + csize = (DWORD)fp->fs->sects_clust * SS(fp->fs); /* Cluster size in unit of byte */ for (;;) { /* Loop to skip leading clusters */ fp->curr_clust = clust; /* Update current cluster */ if (ofs <= csize) break; #if !_FS_READONLY if (fp->flag & FA_WRITE) /* Check if in write mode or not */ - clust = create_chain(fs, clust); /* Force streached if in write mode */ + clust = create_chain(fp->fs, clust); /* Force streached if in write mode */ else #endif - clust = get_cluster(fs, clust); /* Only follow cluster chain if not in write mode */ + clust = get_cluster(fp->fs, clust); /* Only follow cluster chain if not in write mode */ if (clust == 0) { /* Stop if could not follow the cluster chain */ ofs = csize; break; } - if (clust == 1 || clust >= fs->max_clust) goto fk_error; + if (clust == 1 || clust >= fp->fs->max_clust) goto fk_error; fp->fptr += csize; /* Update R/W pointer */ ofs -= csize; } - csect = (BYTE)((ofs - 1) / S_SIZ); /* Sector offset in the cluster */ - fp->curr_sect = clust2sect(fs, clust) + csect; /* Current sector */ - if ((ofs & (S_SIZ - 1)) && /* Load current sector if needed */ - disk_read(fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) + csect = (BYTE)((ofs - 1) / SS(fp->fs)); /* Sector offset in the cluster */ + fp->curr_sect = clust2sect(fp->fs, clust) + csect; /* Current sector */ + if ((ofs & (SS(fp->fs) - 1)) && /* Load current sector if needed */ + disk_read(fp->fs->drive, fp->buffer, fp->curr_sect, 1) != RES_OK) goto fk_error; - fp->sect_clust = fs->sects_clust - csect; /* Left sector counter in the cluster */ - fp->fptr += ofs; /* Update file R/W pointer */ + fp->sect_clust = fp->fs->sects_clust - csect; /* Left sector counter in the cluster */ + fp->fptr += ofs; /* Update file R/W pointer */ } } #if !_FS_READONLY @@ -1211,33 +1214,32 @@ fk_error: /* Abort this file due to an unrecoverable error */ /*-----------------------------------------------------------------------*/ FRESULT f_opendir ( - DIR *dirobj, /* Pointer to directory object to create */ + DIR *dj, /* Pointer to directory object to create */ const char *path /* Pointer to the directory path */ ) { + FRESULT res; BYTE *dir; char fn[8+3+1]; - FRESULT res; - FATFS *fs; - res = auto_mount(&path, &fs, 0); - if (res != FR_OK) return res; - dirobj->fs = fs; - - res = trace_path(dirobj, fn, path, &dir); /* Trace the directory path */ - if (res == FR_OK) { /* Trace completed */ - if (dir != NULL) { /* It is not the root dir */ - if (dir[DIR_Attr] & AM_DIR) { /* The entry is a directory */ - dirobj->clust = ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | LD_WORD(&dir[DIR_FstClusLO]); - dirobj->sect = clust2sect(fs, dirobj->clust); - dirobj->index = 2; - } else { /* The entry is not a directory */ - res = FR_NO_FILE; + res = auto_mount(&path, &dj->fs, 0); + if (res == FR_OK) { + res = trace_path(dj, fn, path, &dir); /* Trace the directory path */ + if (res == FR_OK) { /* Trace completed */ + if (dir) { /* It is not the root dir */ + if (dir[DIR_Attr] & AM_DIR) { /* The entry is a directory */ + dj->clust = ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | LD_WORD(&dir[DIR_FstClusLO]); + dj->sect = clust2sect(dj->fs, dj->clust); + dj->index = 2; + } else { /* The entry is not a directory */ + res = FR_NO_FILE; + } } + dj->id = dj->fs->id; } - dirobj->id = fs->id; } + return res; } @@ -1249,28 +1251,27 @@ FRESULT f_opendir ( /*-----------------------------------------------------------------------*/ FRESULT f_readdir ( - DIR *dirobj, /* Pointer to the directory object */ + DIR *dj, /* Pointer to the directory object */ FILINFO *finfo /* Pointer to file information to return */ ) { BYTE *dir, c, res; - FATFS *fs = dirobj->fs; - res = validate(fs, dirobj->id); /* Check validity of the object */ + res = validate(dj->fs, dj->id); /* Check validity of the object */ if (res != FR_OK) return res; finfo->fname[0] = 0; - while (dirobj->sect) { - if (!move_window(fs, dirobj->sect)) + while (dj->sect) { + if (!move_window(dj->fs, dj->sect)) return FR_RW_ERROR; - dir = &fs->win[(dirobj->index & ((S_SIZ - 1) >> 5)) * 32]; /* pointer to the directory entry */ - c = *dir; - if (c == 0) break; /* Has it reached to end of dir? */ - if (c != 0xE5 && !(dir[DIR_Attr] & AM_VOL)) /* Is it a valid entry? */ + dir = &dj->fs->win[(dj->index & ((SS(dj->fs) - 1) >> 5)) * 32]; /* pointer to the directory entry */ + c = dir[DIR_Name]; + if (c == 0) break; /* Has it reached to end of dir? */ + if (c != 0xE5 && !(dir[DIR_Attr] & AM_VOL)) /* Is it a valid entry? */ get_fileinfo(finfo, dir); - if (!next_dir_entry(dirobj)) dirobj->sect = 0; /* Next entry */ - if (finfo->fname[0]) break; /* Found valid entry */ + if (!next_dir_entry(dj)) dj->sect = 0; /* Next entry */ + if (finfo->fname[0]) break; /* Found valid entry */ } return FR_OK; @@ -1289,23 +1290,21 @@ FRESULT f_stat ( FILINFO *finfo /* Pointer to file information to return */ ) { + FRESULT res; + DIR dj; BYTE *dir; char fn[8+3+1]; - FRESULT res; - DIR dirobj; - FATFS *fs; - res = auto_mount(&path, &fs, 0); - if (res != FR_OK) return res; - dirobj.fs = fs; - - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res == FR_OK) { /* Trace completed */ - if (dir) /* Found an object */ - get_fileinfo(finfo, dir); - else /* It is root dir */ - res = FR_INVALID_NAME; + res = auto_mount(&path, &dj.fs, 0); + if (res == FR_OK) { + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) { /* Trace completed */ + if (dir) /* Found an object */ + get_fileinfo(finfo, dir); + else /* It is root dir */ + res = FR_INVALID_NAME; + } } return res; @@ -1314,49 +1313,90 @@ FRESULT f_stat ( #if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL *fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD ncl; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) return res; + if (fp->flag & FA__ERROR) return FR_RW_ERROR; /* Check error flag */ + if (!(fp->flag & FA_WRITE)) return FR_DENIED; /* Check access mode */ + + if (fp->fsize > fp->fptr) { + fp->fsize = fp->fptr; /* Set file size to current R/W point */ + fp->flag |= FA__WRITTEN; + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + if (!remove_chain(fp->fs, fp->org_clust)) goto ft_error; + fp->org_clust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_cluster(fp->fs, fp->curr_clust); + if (ncl < 2) goto ft_error; + if (ncl < fp->fs->max_clust) { + if (!put_cluster(fp->fs, fp->curr_clust, 0x0FFFFFFF)) goto ft_error; + if (!remove_chain(fp->fs, ncl)) goto ft_error; + } + } + } + + return FR_OK; + +ft_error: /* Abort this file due to an unrecoverable error */ + fp->flag |= FA__ERROR; + return FR_RW_ERROR; +} + + + + /*-----------------------------------------------------------------------*/ /* Get Number of Free Clusters */ /*-----------------------------------------------------------------------*/ FRESULT f_getfree ( - const char *drv, /* Logical drive number */ - DWORD *nclust, /* Pointer to the double word to return number of free clusters */ - FATFS **fatfs /* Pointer to pointer to the file system object to return */ + const char *drv, /* Pointer to the logical drive number (root dir) */ + DWORD *nclust, /* Pointer to the variable to return number of free clusters */ + FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ ) { + FRESULT res; DWORD n, clust, sect; BYTE fat, f, *p; - FRESULT res; - FATFS *fs; /* Get drive number */ - res = auto_mount(&drv, &fs, 0); + res = auto_mount(&drv, fatfs, 0); if (res != FR_OK) return res; - *fatfs = fs; /* If number of free cluster is valid, return it without cluster scan. */ - if (fs->free_clust <= fs->max_clust - 2) { - *nclust = fs->free_clust; + if ((*fatfs)->free_clust <= (*fatfs)->max_clust - 2) { + *nclust = (*fatfs)->free_clust; return FR_OK; } - /* Count number of free clusters */ - fat = fs->fs_type; + /* Get number of free clusters */ + fat = (*fatfs)->fs_type; n = 0; if (fat == FS_FAT12) { clust = 2; do { - if ((WORD)get_cluster(fs, clust) == 0) n++; - } while (++clust < fs->max_clust); + if ((WORD)get_cluster(*fatfs, clust) == 0) n++; + } while (++clust < (*fatfs)->max_clust); } else { - clust = fs->max_clust; - sect = fs->fatbase; + clust = (*fatfs)->max_clust; + sect = (*fatfs)->fatbase; f = 0; p = 0; do { if (!f) { - if (!move_window(fs, sect++)) return FR_RW_ERROR; - p = fs->win; + if (!move_window(*fatfs, sect++)) return FR_RW_ERROR; + p = (*fatfs)->win; } if (fat == FS_FAT16) { if (LD_WORD(p) == 0) n++; @@ -1367,9 +1407,9 @@ FRESULT f_getfree ( } } while (--clust); } - fs->free_clust = n; + (*fatfs)->free_clust = n; #if _USE_FSINFO - if (fat == FS_FAT32) fs->fsi_flag = 1; + if (fat == FS_FAT32) (*fatfs)->fsi_flag = 1; #endif *nclust = n; @@ -1380,51 +1420,48 @@ FRESULT f_getfree ( /*-----------------------------------------------------------------------*/ -/* Delete a File or a Directory */ +/* Delete a File or Directory */ /*-----------------------------------------------------------------------*/ FRESULT f_unlink ( - const char *path /* Pointer to the file or directory path */ + const char *path /* Pointer to the file or directory path */ ) { + FRESULT res; + DIR dj; BYTE *dir, *sdir; DWORD dclust, dsect; char fn[8+3+1]; - FRESULT res; - DIR dirobj; - FATFS *fs; - res = auto_mount(&path, &fs, 1); + res = auto_mount(&path, &dj.fs, 1); if (res != FR_OK) return res; - dirobj.fs = fs; - - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res != FR_OK) return res; /* Trace failed */ - if (dir == NULL) return FR_INVALID_NAME; /* It is the root directory */ + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res != FR_OK) return res; /* Trace failed */ + if (!dir) return FR_INVALID_NAME; /* It is the root directory */ if (dir[DIR_Attr] & AM_RDO) return FR_DENIED; /* It is a R/O object */ - dsect = fs->winsect; + dsect = dj.fs->winsect; dclust = ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | LD_WORD(&dir[DIR_FstClusLO]); - if (dir[DIR_Attr] & AM_DIR) { /* It is a sub-directory */ - dirobj.clust = dclust; /* Check if the sub-dir is empty or not */ - dirobj.sect = clust2sect(fs, dclust); - dirobj.index = 2; + if (dir[DIR_Attr] & AM_DIR) { /* It is a sub-directory */ + dj.clust = dclust; /* Check if the sub-dir is empty or not */ + dj.sect = clust2sect(dj.fs, dclust); + dj.index = 2; do { - if (!move_window(fs, dirobj.sect)) return FR_RW_ERROR; - sdir = &fs->win[(dirobj.index & ((S_SIZ - 1) >> 5)) * 32]; + if (!move_window(dj.fs, dj.sect)) return FR_RW_ERROR; + sdir = &dj.fs->win[(dj.index & ((SS(dj.fs) - 1) >> 5)) * 32]; if (sdir[DIR_Name] == 0) break; if (sdir[DIR_Name] != 0xE5 && !(sdir[DIR_Attr] & AM_VOL)) return FR_DENIED; /* The directory is not empty */ - } while (next_dir_entry(&dirobj)); + } while (next_dir_entry(&dj)); } - if (!move_window(fs, dsect)) return FR_RW_ERROR; /* Mark the directory entry 'deleted' */ + if (!move_window(dj.fs, dsect)) return FR_RW_ERROR; /* Mark the directory entry 'deleted' */ dir[DIR_Name] = 0xE5; - fs->winflag = 1; - if (!remove_chain(fs, dclust)) return FR_RW_ERROR; /* Remove the cluster chain */ + dj.fs->winflag = 1; + if (!remove_chain(dj.fs, dclust)) return FR_RW_ERROR; /* Remove the cluster chain */ - return sync(fs); + return sync(dj.fs); } @@ -1438,38 +1475,35 @@ FRESULT f_mkdir ( const char *path /* Pointer to the directory path */ ) { + FRESULT res; + DIR dj; BYTE *dir, *fw, n; char fn[8+3+1]; DWORD sect, dsect, dclust, pclust, tim; - FRESULT res; - DIR dirobj; - FATFS *fs; - res = auto_mount(&path, &fs, 1); + res = auto_mount(&path, &dj.fs, 1); if (res != FR_OK) return res; - dirobj.fs = fs; - - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res == FR_OK) return FR_EXIST; /* Any file or directory is already existing */ + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) return FR_EXIST; /* Any file or directory is already existing */ if (res != FR_NO_FILE) return res; - res = reserve_direntry(&dirobj, &dir); /* Reserve a directory entry */ + res = reserve_direntry(&dj, &dir); /* Reserve a directory entry */ if (res != FR_OK) return res; - sect = fs->winsect; - dclust = create_chain(fs, 0); /* Allocate a cluster for new directory table */ + sect = dj.fs->winsect; + dclust = create_chain(dj.fs, 0); /* Allocate a cluster for new directory table */ if (dclust == 1) return FR_RW_ERROR; - dsect = clust2sect(fs, dclust); + dsect = clust2sect(dj.fs, dclust); if (!dsect) return FR_DENIED; - if (!move_window(fs, dsect)) return FR_RW_ERROR; + if (!move_window(dj.fs, dsect)) return FR_RW_ERROR; - fw = fs->win; - memset(fw, 0, S_SIZ); /* Clear the new directory table */ - for (n = 1; n < fs->sects_clust; n++) { - if (disk_write(fs->drive, fw, ++dsect, 1) != RES_OK) + fw = dj.fs->win; + memset(fw, 0, SS(dj.fs)); /* Clear the new directory table */ + for (n = 1; n < dj.fs->sects_clust; n++) { + if (disk_write(dj.fs->drive, fw, ++dsect, 1) != RES_OK) return FR_RW_ERROR; } - memset(&fw[DIR_Name], ' ', 8+3); /* Create "." entry */ + memset(&fw[DIR_Name], ' ', 8+3); /* Create "." entry */ fw[DIR_Name] = '.'; fw[DIR_Attr] = AM_DIR; tim = get_fattime(); @@ -1477,13 +1511,13 @@ FRESULT f_mkdir ( memcpy(&fw[32], &fw[0], 32); fw[33] = '.'; /* Create ".." entry */ ST_WORD(&fw[ DIR_FstClusLO], dclust); ST_WORD(&fw[ DIR_FstClusHI], dclust >> 16); - pclust = dirobj.sclust; - if (fs->fs_type == FS_FAT32 && pclust == fs->dirbase) pclust = 0; + pclust = dj.sclust; + if (dj.fs->fs_type == FS_FAT32 && pclust == dj.fs->dirbase) pclust = 0; ST_WORD(&fw[32+DIR_FstClusLO], pclust); ST_WORD(&fw[32+DIR_FstClusHI], pclust >> 16); - fs->winflag = 1; + dj.fs->winflag = 1; - if (!move_window(fs, sect)) return FR_RW_ERROR; + if (!move_window(dj.fs, sect)) return FR_RW_ERROR; memset(&dir[0], 0, 32); /* Initialize the new entry */ memcpy(&dir[DIR_Name], fn, 8+3); /* Name */ dir[DIR_NTres] = fn[11]; @@ -1492,7 +1526,7 @@ FRESULT f_mkdir ( ST_WORD(&dir[DIR_FstClusLO], dclust); /* Table start cluster */ ST_WORD(&dir[DIR_FstClusHI], dclust >> 16); - return sync(fs); + return sync(dj.fs); } @@ -1509,23 +1543,55 @@ FRESULT f_chmod ( ) { FRESULT res; + DIR dj; BYTE *dir; - DIR dirobj; char fn[8+3+1]; - FATFS *fs; - res = auto_mount(&path, &fs, 1); + res = auto_mount(&path, &dj.fs, 1); if (res == FR_OK) { - dirobj.fs = fs; - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res == FR_OK) { /* Trace completed */ - if (dir == NULL) { - res = FR_INVALID_NAME; + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) { /* Trace completed */ + if (!dir) { + res = FR_INVALID_NAME; /* Root directory */ } else { mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ dir[DIR_Attr] = (value & mask) | (dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ - res = sync(fs); + res = sync(dj.fs); + } + } + } + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + const char *path, /* Pointer to the file/directory name */ + const FILINFO *finfo /* Pointer to the timestamp to be set */ +) +{ + FRESULT res; + DIR dj; + BYTE *dir; + char fn[8+3+1]; + + + res = auto_mount(&path, &dj.fs, 1); + if (res == FR_OK) { + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) { /* Trace completed */ + if (!dir) { + res = FR_INVALID_NAME; /* Root directory */ + } else { + ST_WORD(&dir[DIR_WrtTime], finfo->ftime); + ST_WORD(&dir[DIR_WrtDate], finfo->fdate); + res = sync(dj.fs); } } } @@ -1545,38 +1611,36 @@ FRESULT f_rename ( ) { FRESULT res; + DIR dj; DWORD sect_old; BYTE *dir_old, *dir_new, direntry[32-11]; - DIR dirobj; char fn[8+3+1]; - FATFS *fs; - res = auto_mount(&path_old, &fs, 1); + res = auto_mount(&path_old, &dj.fs, 1); if (res != FR_OK) return res; - dirobj.fs = fs; - res = trace_path(&dirobj, fn, path_old, &dir_old); /* Check old object */ - if (res != FR_OK) return res; /* The old object is not found */ + res = trace_path(&dj, fn, path_old, &dir_old); /* Check old object */ + if (res != FR_OK) return res; /* The old object is not found */ if (!dir_old) return FR_NO_FILE; - sect_old = fs->winsect; /* Save the object information */ + sect_old = dj.fs->winsect; /* Save the object information */ memcpy(direntry, &dir_old[DIR_Attr], 32-11); - res = trace_path(&dirobj, fn, path_new, &dir_new); /* Check new object */ + res = trace_path(&dj, fn, path_new, &dir_new); /* Check new object */ if (res == FR_OK) return FR_EXIST; /* The new object name is already existing */ if (res != FR_NO_FILE) return res; /* Is there no old name? */ - res = reserve_direntry(&dirobj, &dir_new); /* Reserve a directory entry */ + res = reserve_direntry(&dj, &dir_new); /* Reserve a directory entry */ if (res != FR_OK) return res; memcpy(&dir_new[DIR_Attr], direntry, 32-11); /* Create new entry */ memcpy(&dir_new[DIR_Name], fn, 8+3); dir_new[DIR_NTres] = fn[11]; - fs->winflag = 1; + dj.fs->winflag = 1; - if (!move_window(fs, sect_old)) return FR_RW_ERROR; /* Remove old entry */ + if (!move_window(dj.fs, sect_old)) return FR_RW_ERROR; /* Delete old entry */ dir_old[DIR_Name] = 0xE5; - return sync(fs); + return sync(dj.fs); } @@ -1615,7 +1679,7 @@ FRESULT f_mkfs ( /* Check mounted drive and clear work area */ fs = FatFs[drv]; if (!fs) return FR_NOT_ENABLED; - memset(fs, 0, sizeof(FATFS)); + fs->fs_type = 0; drv = LD2PD(drv); /* Get disk statics */ @@ -1628,33 +1692,33 @@ FRESULT f_mkfs ( b_part = (!partition) ? 63 : 0; /* Boot sector */ n_part -= b_part; #if S_MAX_SIZ > 512 /* Check disk sector size */ - if (disk_ioctl(drv, GET_SECTOR_SIZE, &S_SIZ) != RES_OK - || S_SIZ > S_MAX_SIZ - || S_SIZ > allocsize) + if (disk_ioctl(drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK + || SS(fs) > S_MAX_SIZ + || SS(fs) > allocsize) return FR_MKFS_ABORTED; #endif - allocsize /= S_SIZ; /* Number of sectors per cluster */ + allocsize /= SS(fs); /* Number of sectors per cluster */ /* Pre-compute number of clusters and FAT type */ n_clust = n_part / allocsize; fmt = FS_FAT12; - if (n_clust >= 0xFF7) fmt = FS_FAT16; - if (n_clust >= 0xFFF7) fmt = FS_FAT32; + if (n_clust >= 0xFF5) fmt = FS_FAT16; + if (n_clust >= 0xFFF5) fmt = FS_FAT32; /* Determine offset and size of FAT structure */ switch (fmt) { case FS_FAT12: - n_fat = ((n_clust * 3 + 1) / 2 + 3 + S_SIZ - 1) / S_SIZ; + n_fat = ((n_clust * 3 + 1) / 2 + 3 + SS(fs) - 1) / SS(fs); n_rsv = 1 + partition; - n_dir = N_ROOTDIR * 32 / S_SIZ; + n_dir = N_ROOTDIR * 32 / SS(fs); break; case FS_FAT16: - n_fat = ((n_clust * 2) + 4 + S_SIZ - 1) / S_SIZ; + n_fat = ((n_clust * 2) + 4 + SS(fs) - 1) / SS(fs); n_rsv = 1 + partition; - n_dir = N_ROOTDIR * 32 / S_SIZ; + n_dir = N_ROOTDIR * 32 / SS(fs); break; default: - n_fat = ((n_clust * 4) + 8 + S_SIZ - 1) / S_SIZ; + n_fat = ((n_clust * 4) + 8 + SS(fs) - 1) / SS(fs); n_rsv = 33 - partition; n_dir = 0; } @@ -1670,8 +1734,8 @@ FRESULT f_mkfs ( /* Determine number of cluster and final check of validity of the FAT type */ n_clust = (n_part - n_rsv - n_fat * N_FATS - n_dir) / allocsize; - if ( (fmt == FS_FAT16 && n_clust < 0xFF7) - || (fmt == FS_FAT32 && n_clust < 0xFFF7)) + if ( (fmt == FS_FAT16 && n_clust < 0xFF5) + || (fmt == FS_FAT32 && n_clust < 0xFFF5)) return FR_MKFS_ABORTED; /* Create partition table if needed */ @@ -1701,13 +1765,13 @@ FRESULT f_mkfs ( /* Create boot record */ tbl = fs->win; /* Clear buffer */ - memset(tbl, 0, S_SIZ); + memset(tbl, 0, SS(fs)); ST_DWORD(&tbl[BS_jmpBoot], 0x90FEEB); /* Boot code (jmp $, nop) */ - ST_WORD(&tbl[BPB_BytsPerSec], S_SIZ); /* Sector size */ + ST_WORD(&tbl[BPB_BytsPerSec], SS(fs)); /* Sector size */ tbl[BPB_SecPerClus] = (BYTE)allocsize; /* Sectors per cluster */ ST_WORD(&tbl[BPB_RsvdSecCnt], n_rsv); /* Reserved sectors */ tbl[BPB_NumFATs] = N_FATS; /* Number of FATs */ - ST_WORD(&tbl[BPB_RootEntCnt], S_SIZ / 32 * n_dir); /* Number of rootdir entries */ + ST_WORD(&tbl[BPB_RootEntCnt], SS(fs) / 32 * n_dir); /* Number of rootdir entries */ if (n_part < 0x10000) { /* Number of total sectors */ ST_WORD(&tbl[BPB_TotSec16], n_part); } else { @@ -1739,7 +1803,7 @@ FRESULT f_mkfs ( /* Initialize FAT area */ for (m = 0; m < N_FATS; m++) { - memset(tbl, 0, S_SIZ); /* 1st sector of the FAT */ + memset(tbl, 0, SS(fs)); /* 1st sector of the FAT */ if (fmt != FS_FAT32) { n = (fmt == FS_FAT12) ? 0x00FFFFF8 : 0xFFFFFFF8; ST_DWORD(&tbl[0], n); /* Reserve cluster #0-1 (FAT12/16) */ @@ -1750,7 +1814,7 @@ FRESULT f_mkfs ( } if (disk_write(drv, tbl, b_fat++, 1) != RES_OK) return FR_RW_ERROR; - memset(tbl, 0, S_SIZ); /* Following FAT entries are filled by zero */ + memset(tbl, 0, SS(fs)); /* Following FAT entries are filled by zero */ for (n = 1; n < n_fat; n++) { if (disk_write(drv, tbl, b_fat++, 1) != RES_OK) return FR_RW_ERROR; diff --git a/src/ff.h b/src/ff.h index c3071d3..8cd63d1 100644 --- a/src/ff.h +++ b/src/ff.h @@ -1,11 +1,11 @@ /*--------------------------------------------------------------------------/ -/ FatFs - FAT file system module include file R0.05 (C)ChaN, 2007 +/ FatFs - FAT file system module include file R0.05a (C)ChaN, 2008 /---------------------------------------------------------------------------/ / FatFs module is an experimenal project to implement FAT file system to / cheap microcontrollers. This is a free software and is opened for education, / research and development under license policy of following trems. / -/ Copyright (C) 2007, ChaN, all right reserved. +/ Copyright (C) 2008, ChaN, all right reserved. / / * The FatFs module is a free software and there is no warranty. / * You can use, modify and/or redistribute it for personal, non-profit or @@ -21,18 +21,18 @@ / 1: Enable word access. / 2: Disable word access and use byte-by-byte access instead. / When the architectural byte order of the MCU is big-endian and/or address -/ miss-aligned access results incorrect behavior, the _MCU_ENDIAN must be set -/ to 2. If it is not the case, it can be set to 1 for good code efficiency. */ +/ miss-aligned access results incorrect behavior, the _MCU_ENDIAN must be set to 2. +/ If it is not the case, it can also be set to 1 for good code efficiency. */ #define _FS_READONLY 0 /* Setting _FS_READONLY to 1 defines read only configuration. This removes -/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename -/ and useless f_getfree. */ +/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename, +/ f_truncate and useless f_getfree. */ #define _FS_MINIMIZE 0 /* The _FS_MINIMIZE option defines minimization level to remove some functions. / 0: Full function. -/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod and f_rename are removed. +/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod, f_truncate and f_rename are removed. / 2: f_opendir and f_readdir are removed in addition to level 1. / 3: f_lseek is removed in addition to level 2. */ @@ -67,9 +67,9 @@ /* Definitions corresponds to multiple sector size (not tested) */ #define S_MAX_SIZ 512 /* Do not change */ #if S_MAX_SIZ > 512 -#define S_SIZ (fs->s_size) +#define SS(fs) ((fs)->s_size) #else -#define S_SIZ 512 +#define SS(fs) 512 #endif @@ -201,10 +201,12 @@ FRESULT f_opendir (DIR*, const char*); /* Open an existing directory */ FRESULT f_readdir (DIR*, FILINFO*); /* Read a directory item */ FRESULT f_stat (const char*, FILINFO*); /* Get file status */ FRESULT f_getfree (const char*, DWORD*, FATFS**); /* Get number of free clusters on the drive */ +FRESULT f_truncate (FIL*); /* Truncate file */ FRESULT f_sync (FIL*); /* Flush cached data of a writing file */ FRESULT f_unlink (const char*); /* Delete an existing file or directory */ FRESULT f_mkdir (const char*); /* Create a new directory */ FRESULT f_chmod (const char*, BYTE, BYTE); /* Change file/dir attriburte */ +FRESULT f_utime (const char*, const FILINFO*); /* Change file/dir timestamp */ FRESULT f_rename (const char*, const char*); /* Rename/Move a file or directory */ FRESULT f_mkfs (BYTE, BYTE, WORD); /* Create a file system on the drive */ @@ -313,8 +315,7 @@ DWORD get_fattime (void); /* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20 #define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr)) #define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val) #define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val) -#else -#if _MCU_ENDIAN == 2 /* Use byte-by-byte access */ +#elif _MCU_ENDIAN == 2 /* Use byte-by-byte access */ #define LD_WORD(ptr) (WORD)(((WORD)*(volatile BYTE*)((ptr)+1)<<8)|(WORD)*(volatile BYTE*)(ptr)) #define LD_DWORD(ptr) (DWORD)(((DWORD)*(volatile BYTE*)((ptr)+3)<<24)|((DWORD)*(volatile BYTE*)((ptr)+2)<<16)|((WORD)*(volatile BYTE*)((ptr)+1)<<8)|*(volatile BYTE*)(ptr)) #define ST_WORD(ptr,val) *(volatile BYTE*)(ptr)=(BYTE)(val); *(volatile BYTE*)((ptr)+1)=(BYTE)((WORD)(val)>>8) @@ -322,7 +323,6 @@ DWORD get_fattime (void); /* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20 #else #error Do not forget to set _MCU_ENDIAN properly! #endif -#endif #define _FATFS diff --git a/src/integer.h b/src/integer.h index 74fd07f..54f74ca 100644 --- a/src/integer.h +++ b/src/integer.h @@ -1,20 +1,24 @@ +/*-------------------------------------------*/ +/* Integer type definitions for FatFs module */ +/*-------------------------------------------*/ + #ifndef _INTEGER -/* These types are assumed as 16-bit or larger integer */ +/* These types must be 16-bit, 32-bit or larger integer */ typedef signed int INT; typedef unsigned int UINT; -/* These types are assumed as 8-bit integer */ +/* These types must be 8-bit integer */ typedef signed char CHAR; typedef unsigned char UCHAR; typedef unsigned char BYTE; -/* These types are assumed as 16-bit integer */ +/* These types must be 16-bit integer */ typedef signed short SHORT; typedef unsigned short USHORT; typedef unsigned short WORD; -/* These types are assumed as 32-bit integer */ +/* These types must be 32-bit integer */ typedef signed long LONG; typedef unsigned long ULONG; typedef unsigned long DWORD; diff --git a/src/tff.c b/src/tff.c index 7fbbc20..c76dba8 100644 --- a/src/tff.c +++ b/src/tff.c @@ -1,11 +1,11 @@ /*--------------------------------------------------------------------------/ -/ FatFs - Tiny FAT file system module R0.05 (C)ChaN, 2007 +/ FatFs - Tiny FAT file system module R0.05a (C)ChaN, 2008 /---------------------------------------------------------------------------/ / The FatFs module is an experimenal project to implement FAT file system to / cheap microcontrollers. This is a free software and is opened for education, / research and development under license policy of following trems. / -/ Copyright (C) 2007, ChaN, all right reserved. +/ Copyright (C) 2008, ChaN, all right reserved. / / * The FatFs module is a free software and there is no warranty. / * You can use, modify and/or redistribute it for personal, non-profit or @@ -14,14 +14,18 @@ / /---------------------------------------------------------------------------/ / Feb 26, 2006 R0.00 Prototype. +/ / Apr 29, 2006 R0.01 First stable version. +/ / Jun 01, 2006 R0.02 Added FAT12 support. / Removed unbuffered mode. / Fixed a problem on small (<32M) patition. / Jun 10, 2006 R0.02a Added a configuration option (_FS_MINIMUM). +/ / Sep 22, 2006 R0.03 Added f_rename(). / Changed option _FS_MINIMUM to _FS_MINIMIZE. / Dec 09, 2006 R0.03a Improved cluster scan algolithm to write files fast. +/ / Feb 04, 2007 R0.04 Added FAT32 supprt. / Changed some interfaces incidental to FatFs. / Changed f_mountdrv() to f_mount(). @@ -33,7 +37,13 @@ / Fixed some problems corresponds to FAT32 support. / Fixed DBCS name can result FR_INVALID_NAME. / Fixed short seek (<= csize) collapses the file object. +/ / Aug 25, 2007 R0.05 Changed arguments of f_read() and f_write(). +/ Feb 03, 2008 R0.05a Added f_truncate(). +/ Added f_utime(). +/ Fixed off by one error at FAT sub-type determination. +/ Fixed btr in f_read() can be mistruncated. +/ Fixed cached sector is not flushed when create and close without write. /---------------------------------------------------------------------------*/ #include @@ -59,9 +69,9 @@ WORD fsid; /* File system mount ID */ /*-----------------------------------------------------------------------*/ static -BOOL move_window ( /* TRUE: successful, FALSE: failed */ - DWORD sector /* Sector number to make apperance in the FatFs->win */ -) /* Move to zero only writes back dirty window */ +BOOL move_window ( /* TRUE: successful, FALSE: failed */ + DWORD sector /* Sector number to make apperance in the FatFs->win */ +) /* Move to zero only writes back dirty window */ { DWORD wsect; FATFS *fs = FatFs; @@ -123,7 +133,8 @@ FRESULT sync (void) /* FR_OK: successful, FR_RW_ERROR: failed */ } #endif /* Make sure that no pending write process in the physical drive */ - if (disk_ioctl(0, CTRL_SYNC, NULL) != RES_OK) return FR_RW_ERROR; + if (disk_ioctl(0, CTRL_SYNC, NULL) != RES_OK) + return FR_RW_ERROR; return FR_OK; } #endif @@ -136,8 +147,8 @@ FRESULT sync (void) /* FR_OK: successful, FR_RW_ERROR: failed */ /*-----------------------------------------------------------------------*/ static -CLUST get_cluster ( /* 0,>=2: successful, 1: failed */ - CLUST clust /* Cluster# to get the link information */ +CLUST get_cluster ( /* 0,>=2: successful, 1: failed */ + CLUST clust /* Cluster# to get the link information */ ) { WORD wc, bc; @@ -167,7 +178,7 @@ CLUST get_cluster ( /* 0,>=2: successful, 1: failed */ } } - return 1; /* There is no cluster information, or an error occured */ + return 1; /* Out of cluster range, or an error occured */ } @@ -179,9 +190,9 @@ CLUST get_cluster ( /* 0,>=2: successful, 1: failed */ #if !_FS_READONLY static -BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ - CLUST clust, /* Cluster# to change */ - CLUST val /* New value to mark the cluster */ +BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ + CLUST clust, /* Cluster# to change (must be 2 to fs->max_clust-1) */ + CLUST val /* New value to mark the cluster */ ) { WORD bc; @@ -231,8 +242,8 @@ BOOL put_cluster ( /* TRUE: successful, FALSE: failed */ #if !_FS_READONLY static -BOOL remove_chain ( /* TRUE: successful, FALSE: failed */ - CLUST clust /* Cluster# to remove chain from */ +BOOL remove_chain ( /* TRUE: successful, FALSE: failed */ + CLUST clust /* Cluster# to remove chain from */ ) { CLUST nxt; @@ -264,7 +275,7 @@ BOOL remove_chain ( /* TRUE: successful, FALSE: failed */ #if !_FS_READONLY static -CLUST create_chain ( /* 0: no free cluster, 1: error, >=2: new cluster number */ +CLUST create_chain ( /* 0: No free cluster, 1: Error, >=2: New cluster number */ CLUST clust /* Cluster# to stretch, 0 means create new */ ) { @@ -341,30 +352,29 @@ DWORD clust2sect ( /* !=0: sector number, 0: failed - invalid cluster# */ static BOOL next_dir_entry ( /* TRUE: successful, FALSE: could not move next */ - DIR *dirobj /* Pointer to directory object */ + DIR *dj /* Pointer to directory object */ ) { CLUST clust; WORD idx; - FATFS *fs = FatFs; - idx = dirobj->index + 1; + idx = dj->index + 1; if ((idx & 15) == 0) { /* Table sector changed? */ - dirobj->sect++; /* Next sector */ - if (!dirobj->clust) { /* In static table */ - if (idx >= fs->n_rootdir) return FALSE; /* Reached to end of table */ - } else { /* In dynamic table */ - if (((idx / 16) & (fs->sects_clust - 1)) == 0) { /* Cluster changed? */ - clust = get_cluster(dirobj->clust); /* Get next cluster */ - if (clust < 2 || clust >= fs->max_clust) /* Reached to end of table */ + dj->sect++; /* Next sector */ + if (!dj->clust) { /* In static table */ + if (idx >= dj->fs->n_rootdir) return FALSE; /* Reached to end of table */ + } else { /* In dynamic table */ + if (((idx / 16) & (dj->fs->sects_clust - 1)) == 0) { /* Cluster changed? */ + clust = get_cluster(dj->clust); /* Get next cluster */ + if (clust < 2 || clust >= dj->fs->max_clust) /* Reached to end of table */ return FALSE; - dirobj->clust = clust; /* Initialize for new cluster */ - dirobj->sect = clust2sect(clust); + dj->clust = clust; /* Initialize for new cluster */ + dj->sect = clust2sect(clust); } } } - dirobj->index = idx; /* Lower 4 bit of dirobj->index indicates offset in dirobj->sect */ + dj->index = idx; /* Lower 4 bit of dj->index indicates offset in dj->sect */ return TRUE; } @@ -377,9 +387,9 @@ BOOL next_dir_entry ( /* TRUE: successful, FALSE: could not move next */ #if _FS_MINIMIZE <= 1 static -void get_fileinfo ( /* No return code */ - FILINFO *finfo, /* Ptr to store the File Information */ - const BYTE *dir /* Ptr to the directory entry */ +void get_fileinfo ( /* No return code */ + FILINFO *finfo, /* Ptr to store the File Information */ + const BYTE *dir /* Ptr to the directory entry */ ) { BYTE n, c, a; @@ -421,9 +431,9 @@ void get_fileinfo ( /* No return code */ /*-----------------------------------------------------------------------*/ static -char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next character */ - const char **path, /* Pointer to the file path pointer */ - char *dirname /* Pointer to directory name buffer {Name(8), Ext(3), NT flag(1)} */ +char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next character */ + const char **path, /* Pointer to the file path pointer */ + char *dirname /* Pointer to directory name buffer {Name(8), Ext(3), NT flag(1)} */ ) { BYTE n, t, c, a, b; @@ -462,14 +472,14 @@ char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next if (c == '|') break; /* Reject | */ if (c >= '[' && c <= ']') break;/* Reject [ \ ] */ if (_USE_NTFLAG && c >= 'A' && c <= 'Z') - (t == 8) ? (b &= ~0x08) : (b &= ~0x10); + (t == 8) ? (b &= 0xF7) : (b &= 0xEF); if (c >= 'a' && c <= 'z') { /* Convert to upper case */ c -= 0x20; if (_USE_NTFLAG) (t == 8) ? (a |= 0x08) : (a |= 0x10); } } md_l1: - a &= ~1; + a &= 0xFE; md_l2: if (n >= t) break; dirname[n++] = c; @@ -485,10 +495,10 @@ char make_dirfile ( /* 1: error - detected an invalid format, '\0'or'/': next static FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ - DIR *dirobj, /* Pointer to directory object to return last directory */ + DIR *dj, /* Pointer to directory object to return last directory */ char *fn, /* Pointer to last segment name to return */ const char *path, /* Full-path string to trace a file or directory */ - BYTE **dir /* Directory pointer in Win[] to retutn */ + BYTE **dir /* Pointer to pointer to found entry to retutn */ ) { CLUST clust; @@ -497,19 +507,19 @@ FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ FATFS *fs = FatFs; /* Initialize directory object */ + dj->fs = fs; clust = fs->dirbase; #if _FAT32 if (fs->fs_type == FS_FAT32) { - dirobj->clust = dirobj->sclust = clust; - dirobj->sect = clust2sect(clust); + dj->clust = dj->sclust = clust; + dj->sect = clust2sect(clust); } else #endif { - dirobj->clust = dirobj->sclust = 0; - dirobj->sect = clust; + dj->clust = dj->sclust = 0; + dj->sect = clust; } - dirobj->index = 0; - dirobj->fs = fs; + dj->index = 0; if (*path == '\0') { /* Null path means the root directory */ *dir = NULL; return FR_OK; @@ -519,26 +529,26 @@ FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ ds = make_dirfile(&path, fn); /* Get a paragraph into fn[] */ if (ds == 1) return FR_INVALID_NAME; for (;;) { - if (!move_window(dirobj->sect)) return FR_RW_ERROR; - dptr = &fs->win[(dirobj->index & 15) * 32]; /* Pointer to the directory entry */ - if (dptr[DIR_Name] == 0) /* Has it reached to end of dir? */ + if (!move_window(dj->sect)) return FR_RW_ERROR; + dptr = &fs->win[(dj->index & 15) * 32]; /* Pointer to the directory entry */ + if (dptr[DIR_Name] == 0) /* Has it reached to end of dir? */ return !ds ? FR_NO_FILE : FR_NO_PATH; - if (dptr[DIR_Name] != 0xE5 /* Matched? */ + if (dptr[DIR_Name] != 0xE5 /* Matched? */ && !(dptr[DIR_Attr] & AM_VOL) && !memcmp(&dptr[DIR_Name], fn, 8+3) ) break; - if (!next_dir_entry(dirobj)) /* Next directory pointer */ + if (!next_dir_entry(dj)) /* Next directory pointer */ return !ds ? FR_NO_FILE : FR_NO_PATH; } - if (!ds) { *dir = dptr; return FR_OK; } /* Matched with end of path */ + if (!ds) { *dir = dptr; return FR_OK; } /* Matched with end of path */ if (!(dptr[DIR_Attr] & AM_DIR)) return FR_NO_PATH; /* Cannot trace because it is a file */ - clust = /* Get cluster# of the directory */ + clust = /* Get cluster# of the directory */ #if _FAT32 ((DWORD)LD_WORD(&dptr[DIR_FstClusHI]) << 16) | #endif LD_WORD(&dptr[DIR_FstClusLO]); - dirobj->clust = dirobj->sclust = clust; /* Restart scannig with the new directory */ - dirobj->sect = clust2sect(clust); - dirobj->index = 2; + dj->clust = dj->sclust = clust; /* Restart scannig with the new directory */ + dj->sect = clust2sect(clust); + dj->index = 2; } } @@ -551,41 +561,41 @@ FRESULT trace_path ( /* FR_OK(0): successful, !=0: error code */ #if !_FS_READONLY static FRESULT reserve_direntry ( /* FR_OK: successful, FR_DENIED: no free entry, FR_RW_ERROR: a disk error occured */ - DIR *dirobj, /* Target directory to create new entry */ + DIR *dj, /* Target directory to create new entry */ BYTE **dir /* Pointer to pointer to created entry to retutn */ ) { CLUST clust; DWORD sector; BYTE c, n, *dptr; - FATFS *fs = FatFs; + FATFS *fs = dj->fs; /* Re-initialize directory object */ - clust = dirobj->sclust; + clust = dj->sclust; if (clust) { /* Dyanmic directory table */ - dirobj->clust = clust; - dirobj->sect = clust2sect(clust); + dj->clust = clust; + dj->sect = clust2sect(clust); } else { /* Static directory table */ - dirobj->sect = fs->dirbase; + dj->sect = fs->dirbase; } - dirobj->index = 0; + dj->index = 0; do { - if (!move_window(dirobj->sect)) return FR_RW_ERROR; - dptr = &fs->win[(dirobj->index & 15) * 32]; /* Pointer to the directory entry */ + if (!move_window(dj->sect)) return FR_RW_ERROR; + dptr = &fs->win[(dj->index & 15) * 32]; /* Pointer to the directory entry */ c = dptr[DIR_Name]; if (c == 0 || c == 0xE5) { /* Found an empty entry! */ *dir = dptr; return FR_OK; } - } while (next_dir_entry(dirobj)); /* Next directory pointer */ + } while (next_dir_entry(dj)); /* Next directory pointer */ /* Reached to end of the directory table */ /* Abort when static table or could not stretch dynamic table */ - if (!clust || !(clust = create_chain(dirobj->clust))) return FR_DENIED; + if (!clust || !(clust = create_chain(dj->clust))) return FR_DENIED; if (clust == 1 || !move_window(0)) return FR_RW_ERROR; - fs->winsect = sector = clust2sect(clust); /* Cleanup the expanded table */ + fs->winsect = sector = clust2sect(clust); /* Cleanup the expanded table */ memset(fs->win, 0, 512); for (n = fs->sects_clust; n; n--) { if (disk_write(0, fs->win, sector, 1) != RES_OK) @@ -602,12 +612,12 @@ FRESULT reserve_direntry ( /* FR_OK: successful, FR_DENIED: no free entry, FR_RW /*-----------------------------------------------------------------------*/ -/* Load boot record and check if it is a FAT boot record */ +/* Load boot record and check if it is an FAT boot record */ /*-----------------------------------------------------------------------*/ static -BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2:Not a boot record or error */ - DWORD sect /* Sector# to check if it is a FAT boot record or not */ +BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2:Not a boot record or error */ + DWORD sect /* Sector# to check if it is an FAT boot record or not */ ) { FATFS *fs = FatFs; @@ -634,17 +644,16 @@ BYTE check_fs ( /* 0:The FAT boot record, 1:Valid boot record but not an FAT, 2 /*-----------------------------------------------------------------------*/ static -FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ - const char **path, /* Pointer to pointer to the path name (drive number) */ - BYTE chk_wp /* !=0: Check media write protection for write access */ +FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ + const char **path, /* Pointer to pointer to the path name (drive number) */ + BYTE chk_wp /* !=0: Check media write protection for write access */ ) { BYTE fmt; DSTATUS stat; DWORD bootsect, fatsize, totalsect, maxclust; const char *p = *path; - FATFS *fs = FatFs; - + FATFS *fs; while (*p == ' ') p++; /* Strip leading spaces */ @@ -652,21 +661,21 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ *path = p; /* Return pointer to the path name */ /* Is the file system object registered? */ + fs = FatFs; if (!fs) return FR_NOT_ENABLED; - /* Chekck if the logical drive has been mounted or not */ - if (fs->fs_type) { + if (fs->fs_type) { /* If the logical drive has been mounted */ stat = disk_status(0); - if (!(stat & STA_NOINIT)) { /* If the physical drive is kept initialized */ + if (!(stat & STA_NOINIT)) { /* and physical drive is kept initialized (has not been changed), */ #if !_FS_READONLY if (chk_wp && (stat & STA_PROTECT)) /* Check write protection if needed */ return FR_WRITE_PROTECTED; #endif - return FR_OK; /* The file system object is valid */ + return FR_OK; /* The file system object is valid */ } } - /* The logical drive has not been mounted, following code attempts to mount the logical drive */ + /* The logical drive must be re-mounted. Following code attempts to mount the logical drive */ memset(fs, 0, sizeof(FATFS)); /* Clean-up the file system object */ stat = disk_initialize(0); /* Initialize low level disk I/O layer */ @@ -679,11 +688,11 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ /* Search FAT partition on the drive */ fmt = check_fs(bootsect = 0); /* Check sector 0 as an SFD format */ - if (fmt == 1) { /* Not a FAT boot record, it may be patitioned */ + if (fmt == 1) { /* Not an FAT boot record, it may be patitioned */ /* Check a partition listed in top of the partition table */ - if (fs->win[MBR_Table+4]) { /* Is the 1st partition existing? */ + if (fs->win[MBR_Table+4]) { /* Is the 1st partition existing? */ bootsect = LD_DWORD(&fs->win[MBR_Table+8]); /* Partition offset in LBA */ - fmt = check_fs(bootsect); /* Check the partition */ + fmt = check_fs(bootsect); /* Check the partition */ } } if (fmt || LD_WORD(&fs->win[BPB_BytsPerSec]) != 512) /* No valid FAT patition is found */ @@ -700,13 +709,13 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ fs->n_rootdir = LD_WORD(&fs->win[BPB_RootEntCnt]); /* Nmuber of root directory entries */ totalsect = LD_WORD(&fs->win[BPB_TotSec16]); /* Number of sectors on the file system */ if (!totalsect) totalsect = LD_DWORD(&fs->win[BPB_TotSec32]); - fs->max_clust = maxclust = (totalsect /* Last cluster# + 1 */ + fs->max_clust = maxclust = (totalsect /* max_clust = Last cluster# + 1 */ - LD_WORD(&fs->win[BPB_RsvdSecCnt]) - fatsize - fs->n_rootdir / 16 ) / fs->sects_clust + 2; fmt = FS_FAT12; /* Determine the FAT sub type */ - if (maxclust > 0xFF7) fmt = FS_FAT16; - if (maxclust > 0xFFF7) + if (maxclust >= 0xFF7) fmt = FS_FAT16; + if (maxclust >= 0xFFF7) #if !_FAT32 return FR_NO_FILESYSTEM; #else @@ -717,12 +726,12 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ #endif fs->dirbase = fs->fatbase + fatsize; /* Root directory start sector (lba) */ fs->database = fs->fatbase + fatsize + fs->n_rootdir / 16; /* Data start sector (lba) */ - fs->fs_type = fmt; /* FAT sub-type */ #if !_FS_READONLY + /* Initialize allocation information */ fs->free_clust = (CLUST)0xFFFFFFFF; #if _USE_FSINFO - /* Load fsinfo sector if needed */ + /* Get fsinfo if needed */ if (fmt == FS_FAT32) { fs->fsi_sector = bootsect + LD_WORD(&fs->win[BPB_FSInfo]); if (disk_read(0, fs->win, fs->fsi_sector, 1) == RES_OK && @@ -735,7 +744,9 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ } #endif #endif - fs->id = ++fsid; /* File system mount ID */ + + fs->fs_type = fmt; /* FAT syb-type */ + fs->id = ++fsid; /* File system mount ID */ return FR_OK; } @@ -747,12 +758,12 @@ FRESULT auto_mount ( /* FR_OK(0): successful, !=0: any error occured */ /*-----------------------------------------------------------------------*/ static -FRESULT validate ( /* FR_OK(0): The id is valid, !=0: Not valid */ +FRESULT validate ( /* FR_OK(0): The object is valid, !=0: Invalid */ const FATFS *fs, /* Pointer to the file system object */ - WORD id /* id member of the target object to be checked */ + WORD id /* Member id of the target object to be checked */ ) { - if (!fs || fs->id != id) + if (!fs || !fs->fs_type || fs->id != id) return FR_INVALID_OBJECT; if (disk_status(0) & STA_NOINIT) return FR_NOT_READY; @@ -779,14 +790,12 @@ FRESULT f_mount ( FATFS *fs /* Pointer to new file system object (NULL for unmount)*/ ) { - FATFS *fsobj; - - if (drv) return FR_INVALID_DRIVE; - fsobj = FatFs; - FatFs = fs; - if (fsobj) memset(fsobj, 0, sizeof(FATFS)); - if (fs) memset(fs, 0, sizeof(FATFS)); + + if (FatFs) FatFs->fs_type = 0; /* Clear old object */ + + FatFs = fs; /* Register and clear new object */ + if (fs) fs->fs_type = 0; return FR_OK; } @@ -805,10 +814,9 @@ FRESULT f_open ( ) { FRESULT res; + DIR dj; BYTE *dir; - DIR dirobj; char fn[8+3+1]; - FATFS *fs = FatFs; fp->fs = NULL; @@ -820,9 +828,7 @@ FRESULT f_open ( res = auto_mount(&path, 0); #endif if (res != FR_OK) return res; - - /* Trace the file path */ - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ #if !_FS_READONLY /* Create or Open a File */ @@ -831,16 +837,17 @@ FRESULT f_open ( DWORD dw; if (res != FR_OK) { /* No file, create new */ if (res != FR_NO_FILE) return res; - res = reserve_direntry(&dirobj, &dir); + res = reserve_direntry(&dj, &dir); if (res != FR_OK) return res; - memset(dir, 0, 32); /* Initialize the new entry */ + memset(dir, 0, 32); /* Initialize the new entry with open name */ memcpy(&dir[DIR_Name], fn, 8+3); dir[DIR_NTres] = fn[11]; mode |= FA_CREATE_ALWAYS; - } else { /* Any object is already existing */ + } + else { /* Any object is already existing */ if (mode & FA_CREATE_NEW) /* Cannot create new */ return FR_EXIST; - if (dir == NULL || (dir[DIR_Attr] & (AM_RDO|AM_DIR))) /* Cannot overwrite (R/O or DIR) */ + if (!dir || (dir[DIR_Attr] & (AM_RDO|AM_DIR))) /* Cannot overwrite (R/O or DIR) */ return FR_DENIED; if (mode & FA_CREATE_ALWAYS) { /* Resize it to zero */ #if _FAT32 @@ -851,45 +858,45 @@ FRESULT f_open ( #endif ST_WORD(&dir[DIR_FstClusLO], 0); /* cluster = 0 */ ST_DWORD(&dir[DIR_FileSize], 0); /* size = 0 */ - fs->winflag = 1; - dw = fs->winsect; /* Remove the cluster chain */ + dj.fs->winflag = 1; + dw = dj.fs->winsect; /* Remove the cluster chain */ if (!remove_chain(rs) || !move_window(dw)) return FR_RW_ERROR; - fs->last_clust = rs - 1; /* Reuse the cluster hole */ + dj.fs->last_clust = rs - 1; /* Reuse the cluster hole */ } } if (mode & FA_CREATE_ALWAYS) { - dir[DIR_Attr] = AM_ARC; /* New attribute */ + dir[DIR_Attr] = 0; /* Reset attribute */ dw = get_fattime(); - ST_DWORD(&dir[DIR_WrtTime], dw); /* Updated time */ ST_DWORD(&dir[DIR_CrtTime], dw); /* Created time */ - fs->winflag = 1; + dj.fs->winflag = 1; + mode |= FA__WRITTEN; /* Set file changed flag */ } } - /* Open a File */ + /* Open an existing file */ else { #endif /* !_FS_READONLY */ - if (res != FR_OK) return res; /* Trace failed */ - if (dir == NULL || (dir[DIR_Attr] & AM_DIR)) /* It is a directory */ + if (res != FR_OK) return res; /* Trace failed */ + if (!dir || (dir[DIR_Attr] & AM_DIR)) /* It is a directory */ return FR_NO_FILE; #if !_FS_READONLY if ((mode & FA_WRITE) && (dir[DIR_Attr] & AM_RDO)) /* R/O violation */ return FR_DENIED; } - fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ + fp->dir_sect = dj.fs->winsect; /* Pointer to the directory entry */ fp->dir_ptr = dir; #endif - fp->flag = mode; /* File access mode */ - fp->org_clust = /* File start cluster */ + fp->flag = mode; /* File access mode */ + fp->org_clust = /* File start cluster */ #if _FAT32 ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | #endif LD_WORD(&dir[DIR_FstClusLO]); fp->fsize = LD_DWORD(&dir[DIR_FileSize]); /* File size */ - fp->fptr = 0; /* File ptr */ - fp->sect_clust = 1; /* Sector counter */ - fp->fs = fs; fp->id = fs->id; /* Owner file system object of the file */ + fp->fptr = 0; /* File ptr */ + fp->sect_clust = 1; /* Sector counter */ + fp->fs = dj.fs; fp->id = dj.fs->id; /* Owner file system object of the file */ return FR_OK; } @@ -908,21 +915,20 @@ FRESULT f_read ( UINT *br /* Pointer to number of bytes read */ ) { + FRESULT res; DWORD sect, remain; UINT rcnt, cc; CLUST clust; BYTE *rbuff = buff; - FRESULT res; - FATFS *fs = fp->fs; *br = 0; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res != FR_OK) return res; if (fp->flag & FA__ERROR) return FR_RW_ERROR; /* Check error flag */ if (!(fp->flag & FA_READ)) return FR_DENIED; /* Check access mode */ remain = fp->fsize - fp->fptr; - if (btr > remain) btr = (WORD)remain; /* Truncate read count by number of bytes left */ + if (btr > remain) btr = (UINT)remain; /* Truncate read count by number of bytes left */ for ( ; btr; /* Repeat until all data transferred */ rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { @@ -932,11 +938,11 @@ FRESULT f_read ( } else { /* On the cluster boundary, get next cluster */ clust = (fp->fptr == 0) ? fp->org_clust : get_cluster(fp->curr_clust); - if (clust < 2 || clust >= fs->max_clust) + if (clust < 2 || clust >= fp->fs->max_clust) goto fr_error; fp->curr_clust = clust; /* Current cluster */ sect = clust2sect(clust); /* Get current sector */ - fp->sect_clust = fs->sects_clust; /* Re-initialize the left sector counter */ + fp->sect_clust = fp->fs->sects_clust; /* Re-initialize the left sector counter */ } fp->curr_sect = sect; /* Update current sector */ cc = btr / 512; /* When left bytes >= 512, */ @@ -951,14 +957,14 @@ FRESULT f_read ( } } if (!move_window(fp->curr_sect)) goto fr_error; /* Move sector window */ - rcnt = 512 - (WORD)(fp->fptr % 512); /* Copy fractional bytes from sector window */ + rcnt = 512 - (fp->fptr % 512); /* Copy fractional bytes from sector window */ if (rcnt > btr) rcnt = btr; - memcpy(rbuff, &fs->win[(WORD)fp->fptr % 512], rcnt); + memcpy(rbuff, &fp->fs->win[fp->fptr % 512], rcnt); } return FR_OK; -fr_error: /* Abort this function due to an unrecoverable error */ +fr_error: /* Abort this file due to an unrecoverable error */ fp->flag |= FA__ERROR; return FR_RW_ERROR; } @@ -978,16 +984,15 @@ FRESULT f_write ( UINT *bw /* Pointer to number of bytes written */ ) { + FRESULT res; DWORD sect; UINT wcnt, cc; CLUST clust; - FRESULT res; const BYTE *wbuff = buff; - FATFS *fs = fp->fs; *bw = 0; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res != FR_OK) return res; if (fp->flag & FA__ERROR) return FR_RW_ERROR; /* Check error flag */ if (!(fp->flag & FA_WRITE)) return FR_DENIED; /* Check access mode */ @@ -996,7 +1001,7 @@ FRESULT f_write ( for ( ; btw; /* Repeat until all data transferred */ wbuff += wcnt, fp->fptr += wcnt, *bw += wcnt, btw -= wcnt) { if ((fp->fptr % 512) == 0) { /* On the sector boundary */ - if (--(fp->sect_clust)) { /* Decrement left sector counter */ + if (--fp->sect_clust) { /* Decrement left sector counter */ sect = fp->curr_sect + 1; /* Get current sector */ } else { /* On the cluster boundary, get next cluster */ if (fp->fptr == 0) { /* Is top of the file */ @@ -1007,10 +1012,10 @@ FRESULT f_write ( clust = create_chain(fp->curr_clust); /* Trace or streach cluster chain */ } if (clust == 0) break; /* Disk full */ - if (clust == 1 || clust >= fs->max_clust) goto fw_error; + if (clust == 1 || clust >= fp->fs->max_clust) goto fw_error; fp->curr_clust = clust; /* Current cluster */ sect = clust2sect(clust); /* Get current sector */ - fp->sect_clust = fs->sects_clust; /* Re-initialize the left sector counter */ + fp->sect_clust = fp->fs->sects_clust; /* Re-initialize the left sector counter */ } fp->curr_sect = sect; /* Update current sector */ cc = btw / 512; /* When left bytes >= 512, */ @@ -1025,22 +1030,22 @@ FRESULT f_write ( } if (fp->fptr >= fp->fsize) { /* Flush R/W window if needed */ if (!move_window(0)) goto fw_error; - fs->winsect = fp->curr_sect; + fp->fs->winsect = fp->curr_sect; } } if (!move_window(fp->curr_sect)) /* Move sector window */ goto fw_error; - wcnt = 512 - (WORD)(fp->fptr % 512); /* Copy fractional bytes bytes to sector window */ + wcnt = 512 - (fp->fptr % 512); /* Copy fractional bytes bytes to sector window */ if (wcnt > btw) wcnt = btw; - memcpy(&fs->win[(WORD)fp->fptr % 512], wbuff, wcnt); - fs->winflag = 1; + memcpy(&fp->fs->win[fp->fptr % 512], wbuff, wcnt); + fp->fs->winflag = 1; } if (fp->fptr > fp->fsize) fp->fsize = fp->fptr; /* Update file size if needed */ fp->flag |= FA__WRITTEN; /* Set file changed flag */ return FR_OK; -fw_error: /* Abort this function due to an unrecoverable error */ +fw_error: /* Abort this file due to an unrecoverable error */ fp->flag |= FA__ERROR; return FR_RW_ERROR; } @@ -1056,15 +1061,14 @@ FRESULT f_sync ( FIL *fp /* Pointer to the file object */ ) { + FRESULT res; DWORD tim; BYTE *dir; - FRESULT res; - FATFS *fs = fp->fs; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res == FR_OK) { - if (fp->flag & FA__WRITTEN) { /* Has the file been written? */ + if (fp->flag & FA__WRITTEN) { /* Has the file been written? */ /* Update the directory entry */ if (!move_window(fp->dir_sect)) return FR_RW_ERROR; @@ -1077,7 +1081,7 @@ FRESULT f_sync ( #endif tim = get_fattime(); /* Updated time */ ST_DWORD(&dir[DIR_WrtTime], tim); - fp->flag &= ~FA__WRITTEN; + fp->flag &= (BYTE)~FA__WRITTEN; res = sync(); } } @@ -1088,7 +1092,6 @@ FRESULT f_sync ( - /*-----------------------------------------------------------------------*/ /* Close File */ /*-----------------------------------------------------------------------*/ @@ -1105,9 +1108,7 @@ FRESULT f_close ( #else res = validate(fp->fs, fp->id); #endif - if (res == FR_OK) - fp->fs = NULL; - + if (res == FR_OK) fp->fs = NULL; return res; } @@ -1116,7 +1117,7 @@ FRESULT f_close ( #if _FS_MINIMIZE <= 2 /*-----------------------------------------------------------------------*/ -/* Seek File Pointer */ +/* Seek File R/W Pointer */ /*-----------------------------------------------------------------------*/ FRESULT f_lseek ( @@ -1124,16 +1125,14 @@ FRESULT f_lseek ( DWORD ofs /* File pointer from top of file */ ) { + FRESULT res; CLUST clust; DWORD csize; BYTE csect; - FRESULT res; - FATFS *fs = fp->fs; - res = validate(fs, fp->id); /* Check validity of the object */ + res = validate(fp->fs, fp->id); /* Check validity of the object */ if (res != FR_OK) return res; - if (fp->flag & FA__ERROR) return FR_RW_ERROR; #if !_FS_READONLY if (ofs > fp->fsize && !(fp->flag & FA_WRITE)) @@ -1154,7 +1153,7 @@ FRESULT f_lseek ( } #endif if (clust) { /* If the file has a cluster chain, it can be followed */ - csize = (DWORD)fs->sects_clust * 512; /* Cluster size in unit of byte */ + csize = (DWORD)fp->fs->sects_clust * 512; /* Cluster size in unit of byte */ for (;;) { /* Loop to skip leading clusters */ fp->curr_clust = clust; /* Update current cluster */ if (ofs <= csize) break; @@ -1167,13 +1166,13 @@ FRESULT f_lseek ( if (clust == 0) { /* Stop if could not follow the cluster chain */ ofs = csize; break; } - if (clust == 1 || clust >= fs->max_clust) goto fk_error; + if (clust == 1 || clust >= fp->fs->max_clust) goto fk_error; fp->fptr += csize; /* Update R/W pointer */ ofs -= csize; } csect = (BYTE)((ofs - 1) / 512); /* Sector offset in the cluster */ fp->curr_sect = clust2sect(clust) + csect; /* Current sector */ - fp->sect_clust = fs->sects_clust - csect; /* Left sector counter in the cluster */ + fp->sect_clust = fp->fs->sects_clust - csect; /* Left sector counter in the cluster */ fp->fptr += ofs; /* Update file R/W pointer */ } } @@ -1186,7 +1185,7 @@ FRESULT f_lseek ( return FR_OK; -fk_error: /* Abort this function due to an unrecoverable error */ +fk_error: /* Abort this file due to an unrecoverable error */ fp->flag |= FA__ERROR; return FR_RW_ERROR; } @@ -1196,40 +1195,40 @@ fk_error: /* Abort this function due to an unrecoverable error */ #if _FS_MINIMIZE <= 1 /*-----------------------------------------------------------------------*/ -/* Open a directroy */ +/* Create a directroy object */ /*-----------------------------------------------------------------------*/ FRESULT f_opendir ( - DIR *dirobj, /* Pointer to directory object to create */ + DIR *dj, /* Pointer to directory object to create */ const char *path /* Pointer to the directory path */ ) { + FRESULT res; BYTE *dir; char fn[8+3+1]; - FRESULT res; - FATFS *fs = FatFs; res = auto_mount(&path, 0); - if (res != FR_OK) return res; - - res = trace_path(dirobj, fn, path, &dir); /* Trace the directory path */ - if (res == FR_OK) { /* Trace completed */ - if (dir != NULL) { /* It is not the root dir */ - if (dir[DIR_Attr] & AM_DIR) { /* The entry is a directory */ - dirobj->clust = + if (res == FR_OK) { + res = trace_path(dj, fn, path, &dir); /* Trace the directory path */ + if (res == FR_OK) { /* Trace completed */ + if (dir) { /* It is not the root dir */ + if (dir[DIR_Attr] & AM_DIR) { /* The entry is a directory */ + dj->clust = #if _FAT32 - ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | + ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | #endif - LD_WORD(&dir[DIR_FstClusLO]); - dirobj->sect = clust2sect(dirobj->clust); - dirobj->index = 2; - } else { /* The entry is not a directory */ - res = FR_NO_FILE; + LD_WORD(&dir[DIR_FstClusLO]); + dj->sect = clust2sect(dj->clust); + dj->index = 2; + } else { /* The entry is not a directory */ + res = FR_NO_FILE; + } } + dj->id = dj->fs->id; } - dirobj->id = fs->id; } + return res; } @@ -1241,29 +1240,28 @@ FRESULT f_opendir ( /*-----------------------------------------------------------------------*/ FRESULT f_readdir ( - DIR *dirobj, /* Pointer to the directory object */ + DIR *dj, /* Pointer to the directory object */ FILINFO *finfo /* Pointer to file information to return */ ) { - BYTE *dir, c; FRESULT res; - FATFS *fs = dirobj->fs; + BYTE *dir, c; - res = validate(fs, dirobj->id); /* Check validity of the object */ + res = validate(dj->fs, dj->id); /* Check validity of the object */ if (res != FR_OK) return res; finfo->fname[0] = 0; - while (dirobj->sect) { - if (!move_window(dirobj->sect)) + while (dj->sect) { + if (!move_window(dj->sect)) return FR_RW_ERROR; - dir = &fs->win[(dirobj->index & 15) * 32]; /* pointer to the directory entry */ + dir = &dj->fs->win[(dj->index & 15) * 32]; /* pointer to the directory entry */ c = dir[DIR_Name]; - if (c == 0) break; /* Has it reached to end of dir? */ - if (c != 0xE5 && !(dir[DIR_Attr] & AM_VOL)) /* Is it a valid entry? */ + if (c == 0) break; /* Has it reached to end of dir? */ + if (c != 0xE5 && !(dir[DIR_Attr] & AM_VOL)) /* Is it a valid entry? */ get_fileinfo(finfo, dir); - if (!next_dir_entry(dirobj)) dirobj->sect = 0; /* Next entry */ - if (finfo->fname[0]) break; /* Found valid entry */ + if (!next_dir_entry(dj)) dj->sect = 0; /* Next entry */ + if (finfo->fname[0]) break; /* Found valid entry */ } return FR_OK; @@ -1282,21 +1280,21 @@ FRESULT f_stat ( FILINFO *finfo /* Pointer to file information to return */ ) { + FRESULT res; + DIR dj; BYTE *dir; char fn[8+3+1]; - FRESULT res; - DIR dirobj; res = auto_mount(&path, 0); - if (res != FR_OK) return res; - - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res == FR_OK) { /* Trace completed */ - if (dir) /* Found an object */ - get_fileinfo(finfo, dir); - else /* It is root dir */ - res = FR_INVALID_NAME; + if (res == FR_OK) { + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) { /* Trace completed */ + if (dir) /* Found an object */ + get_fileinfo(finfo, dir); + else /* It is root dir */ + res = FR_INVALID_NAME; + } } return res; @@ -1306,21 +1304,64 @@ FRESULT f_stat ( #if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL *fp /* Pointer to the file object */ +) +{ + FRESULT res; + CLUST ncl; + + + res = validate(fp->fs, fp->id); /* Check validity of the object */ + if (res != FR_OK) return res; + if (fp->flag & FA__ERROR) return FR_RW_ERROR; /* Check error flag */ + if (!(fp->flag & FA_WRITE)) return FR_DENIED; /* Check access mode */ + + if (fp->fsize > fp->fptr) { + fp->fsize = fp->fptr; /* Set file size to current R/W point */ + fp->flag |= FA__WRITTEN; + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + if (!remove_chain(fp->org_clust)) goto ft_error; + fp->org_clust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_cluster(fp->curr_clust); + if (ncl < 2) goto ft_error; + if (ncl < fp->fs->max_clust) { + if (!put_cluster(fp->curr_clust, (CLUST)0x0FFFFFFF)) goto ft_error; + if (!remove_chain(ncl)) goto ft_error; + } + } + } + + return FR_OK; + +ft_error: /* Abort this file due to an unrecoverable error */ + fp->flag |= FA__ERROR; + return FR_RW_ERROR; +} + + + + /*-----------------------------------------------------------------------*/ /* Get Number of Free Clusters */ /*-----------------------------------------------------------------------*/ FRESULT f_getfree ( - const char *drv, /* Logical drive number */ - DWORD *nclust, /* Pointer to the double word to return number of free clusters */ - FATFS **fatfs /* Pointer to pointer to the file system object to return */ + const char *drv, /* Pointer to the logical drive number (root dir) */ + DWORD *nclust, /* Pointer to the variable to return number of free clusters */ + FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ ) { + FRESULT res; + FATFS *fs; DWORD n, sect; CLUST clust; BYTE fat, f, *p; - FRESULT res; - FATFS *fs; /* Get drive number */ @@ -1334,7 +1375,7 @@ FRESULT f_getfree ( return FR_OK; } - /* Count number of free clusters */ + /* Get number of free clusters */ fat = fs->fs_type; n = 0; if (fat == FS_FAT12) { @@ -1373,51 +1414,49 @@ FRESULT f_getfree ( /*-----------------------------------------------------------------------*/ -/* Delete a File or a Directory */ +/* Delete a File or Directory */ /*-----------------------------------------------------------------------*/ FRESULT f_unlink ( - const char *path /* Pointer to the file or directory path */ + const char *path /* Pointer to the file or directory path */ ) { + FRESULT res; + DIR dj; BYTE *dir, *sdir; DWORD dsect; char fn[8+3+1]; CLUST dclust; - FRESULT res; - DIR dirobj; - FATFS *fs = FatFs; res = auto_mount(&path, 1); if (res != FR_OK) return res; - - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res != FR_OK) return res; /* Trace failed */ - if (dir == NULL) return FR_INVALID_NAME; /* It is the root directory */ + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res != FR_OK) return res; /* Trace failed */ + if (!dir) return FR_INVALID_NAME; /* It is the root directory */ if (dir[DIR_Attr] & AM_RDO) return FR_DENIED; /* It is a R/O object */ - dsect = fs->winsect; + dsect = dj.fs->winsect; dclust = #if _FAT32 ((DWORD)LD_WORD(&dir[DIR_FstClusHI]) << 16) | #endif LD_WORD(&dir[DIR_FstClusLO]); - if (dir[DIR_Attr] & AM_DIR) { /* It is a sub-directory */ - dirobj.clust = dclust; /* Check if the sub-dir is empty or not */ - dirobj.sect = clust2sect(dclust); - dirobj.index = 2; + if (dir[DIR_Attr] & AM_DIR) { /* It is a sub-directory */ + dj.clust = dclust; /* Check if the sub-dir is empty or not */ + dj.sect = clust2sect(dclust); + dj.index = 2; do { - if (!move_window(dirobj.sect)) return FR_RW_ERROR; - sdir = &fs->win[(dirobj.index & 15) * 32]; + if (!move_window(dj.sect)) return FR_RW_ERROR; + sdir = &dj.fs->win[(dj.index & 15) * 32]; if (sdir[DIR_Name] == 0) break; if (sdir[DIR_Name] != 0xE5 && !(sdir[DIR_Attr] & AM_VOL)) return FR_DENIED; /* The directory is not empty */ - } while (next_dir_entry(&dirobj)); + } while (next_dir_entry(&dj)); } if (!move_window(dsect)) return FR_RW_ERROR; /* Mark the directory entry 'deleted' */ dir[DIR_Name] = 0xE5; - fs->winflag = 1; + dj.fs->winflag = 1; if (!remove_chain(dclust)) return FR_RW_ERROR; /* Remove the cluster chain */ return sync(); @@ -1434,53 +1473,51 @@ FRESULT f_mkdir ( const char *path /* Pointer to the directory path */ ) { + FRESULT res; + DIR dj; BYTE *dir, *fw, n; char fn[8+3+1]; DWORD sect, dsect, tim; CLUST dclust, pclust; - FRESULT res; - DIR dirobj; - FATFS *fs = FatFs; res = auto_mount(&path, 1); if (res != FR_OK) return res; - - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res == FR_OK) return FR_EXIST; /* Any file or directory is already existing */ + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) return FR_EXIST; /* Any file or directory is already existing */ if (res != FR_NO_FILE) return res; - res = reserve_direntry(&dirobj, &dir); /* Reserve a directory entry */ + res = reserve_direntry(&dj, &dir); /* Reserve a directory entry */ if (res != FR_OK) return res; - sect = fs->winsect; - dclust = create_chain(0); /* Allocate a cluster for new directory table */ + sect = dj.fs->winsect; + dclust = create_chain(0); /* Allocate a cluster for new directory table */ if (dclust == 1) return FR_RW_ERROR; dsect = clust2sect(dclust); if (!dsect) return FR_DENIED; if (!move_window(dsect)) return FR_RW_ERROR; - fw = fs->win; - memset(fw, 0, 512); /* Clear the directory table */ - for (n = 1; n < fs->sects_clust; n++) { + fw = dj.fs->win; + memset(fw, 0, 512); /* Clear the directory table */ + for (n = 1; n < dj.fs->sects_clust; n++) { if (disk_write(0, fw, ++dsect, 1) != RES_OK) return FR_RW_ERROR; } - memset(&fw[DIR_Name], ' ', 8+3); /* Create "." entry */ + memset(&fw[DIR_Name], ' ', 8+3); /* Create "." entry */ fw[DIR_Name] = '.'; fw[DIR_Attr] = AM_DIR; tim = get_fattime(); ST_DWORD(&fw[DIR_WrtTime], tim); memcpy(&fw[32], &fw[0], 32); fw[33] = '.'; /* Create ".." entry */ - pclust = dirobj.sclust; + pclust = dj.sclust; #if _FAT32 ST_WORD(&fw[ DIR_FstClusHI], dclust >> 16); - if (fs->fs_type == FS_FAT32 && pclust == fs->dirbase) pclust = 0; + if (dj.fs->fs_type == FS_FAT32 && pclust == dj.fs->dirbase) pclust = 0; ST_WORD(&fw[32+DIR_FstClusHI], pclust >> 16); #endif ST_WORD(&fw[ DIR_FstClusLO], dclust); ST_WORD(&fw[32+DIR_FstClusLO], pclust); - fs->winflag = 1; + dj.fs->winflag = 1; if (!move_window(sect)) return FR_RW_ERROR; memset(&dir[0], 0, 32); /* Clean-up the new entry */ @@ -1510,17 +1547,17 @@ FRESULT f_chmod ( ) { FRESULT res; + DIR dj; BYTE *dir; - DIR dirobj; char fn[8+3+1]; res = auto_mount(&path, 1); if (res == FR_OK) { - res = trace_path(&dirobj, fn, path, &dir); /* Trace the file path */ - if (res == FR_OK) { /* Trace completed */ - if (dir == NULL) { - res = FR_INVALID_NAME; + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) { /* Trace completed */ + if (!dir) { + res = FR_INVALID_NAME; /* Root directory */ } else { mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ dir[DIR_Attr] = (value & mask) | (dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ @@ -1534,6 +1571,40 @@ FRESULT f_chmod ( +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + const char *path, /* Pointer to the file/directory name */ + const FILINFO *finfo /* Pointer to the timestamp to be set */ +) +{ + FRESULT res; + DIR dj; + BYTE *dir; + char fn[8+3+1]; + + + res = auto_mount(&path, 1); + if (res == FR_OK) { + res = trace_path(&dj, fn, path, &dir); /* Trace the file path */ + if (res == FR_OK) { /* Trace completed */ + if (!dir) { + res = FR_INVALID_NAME; /* Root directory */ + } else { + ST_WORD(&dir[DIR_WrtTime], finfo->ftime); + ST_WORD(&dir[DIR_WrtDate], finfo->fdate); + res = sync(); + } + } + } + return res; +} + + + + /*-----------------------------------------------------------------------*/ /* Rename File/Directory */ /*-----------------------------------------------------------------------*/ @@ -1546,32 +1617,31 @@ FRESULT f_rename ( FRESULT res; DWORD sect_old; BYTE *dir_old, *dir_new, direntry[32-11]; - DIR dirobj; + DIR dj; char fn[8+3+1]; - FATFS *fs = FatFs; res = auto_mount(&path_old, 1); if (res != FR_OK) return res; - res = trace_path(&dirobj, fn, path_old, &dir_old); /* Check old object */ - if (res != FR_OK) return res; /* The old object is not found */ + res = trace_path(&dj, fn, path_old, &dir_old); /* Check old object */ + if (res != FR_OK) return res; /* The old object is not found */ if (!dir_old) return FR_NO_FILE; - sect_old = fs->winsect; /* Save the object information */ - memcpy(direntry, &dir_old[11], 32-11); + sect_old = dj.fs->winsect; /* Save the object information */ + memcpy(direntry, &dir_old[DIR_Attr], 32-11); - res = trace_path(&dirobj, fn, path_new, &dir_new); /* Check new object */ + res = trace_path(&dj, fn, path_new, &dir_new); /* Check new object */ if (res == FR_OK) return FR_EXIST; /* The new object name is already existing */ if (res != FR_NO_FILE) return res; /* Is there no old name? */ - res = reserve_direntry(&dirobj, &dir_new); /* Reserve a directory entry */ + res = reserve_direntry(&dj, &dir_new); /* Reserve a directory entry */ if (res != FR_OK) return res; memcpy(&dir_new[DIR_Attr], direntry, 32-11); /* Create new entry */ memcpy(&dir_new[DIR_Name], fn, 8+3); dir_new[DIR_NTres] = fn[11]; - fs->winflag = 1; + dj.fs->winflag = 1; - if (!move_window(sect_old)) return FR_RW_ERROR; /* Remove old entry */ + if (!move_window(sect_old)) return FR_RW_ERROR; /* Delete old entry */ dir_old[DIR_Name] = 0xE5; return sync(); diff --git a/src/tff.h b/src/tff.h index 73e3c4c..cdccab4 100644 --- a/src/tff.h +++ b/src/tff.h @@ -1,11 +1,11 @@ /*--------------------------------------------------------------------------/ -/ Tiny-FatFs - FAT file system module include file R0.05 (C)ChaN, 2007 +/ Tiny-FatFs - FAT file system module include file R0.05a (C)ChaN, 2008 /---------------------------------------------------------------------------/ / FatFs module is an experimenal project to implement FAT file system to / cheap microcontrollers. This is a free software and is opened for education, / research and development under license policy of following trems. / -/ Copyright (C) 2007, ChaN, all right reserved. +/ Copyright (C) 2008, ChaN, all right reserved. / / * The FatFs module is a free software and there is no warranty. / * You can use, modify and/or redistribute it for personal, non-profit or @@ -21,18 +21,18 @@ / 1: Enable word access. / 2: Disable word access and use byte-by-byte access instead. / When the architectural byte order of the MCU is big-endian and/or address -/ miss-aligned access results incorrect behavior, the _MCU_ENDIAN must be set -/ to 2. If it is not the case, it can be set to 1 for good code efficiency. */ +/ miss-aligned access results incorrect behavior, the _MCU_ENDIAN must be set to 2. +/ If it is not the case, it can also be set to 1 for good code efficiency. */ #define _FS_READONLY 0 /* Setting _FS_READONLY to 1 defines read only configuration. This removes -/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename -/ and useless f_getfree. */ +/ writing functions, f_write, f_sync, f_unlink, f_mkdir, f_chmod, f_rename, +/ f_truncate, f_getfree and internal writing codes. */ #define _FS_MINIMIZE 0 /* The _FS_MINIMIZE option defines minimization level to remove some functions. / 0: Full function. -/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod and f_rename are removed. +/ 1: f_stat, f_getfree, f_unlink, f_mkdir, f_chmod, f_truncate and f_rename are removed. / 2: f_opendir and f_readdir are removed in addition to level 1. / 3: f_lseek is removed in addition to level 2. */ @@ -163,10 +163,12 @@ FRESULT f_opendir (DIR*, const char*); /* Open an existing directory */ FRESULT f_readdir (DIR*, FILINFO*); /* Read a directory item */ FRESULT f_stat (const char*, FILINFO*); /* Get file status */ FRESULT f_getfree (const char*, DWORD*, FATFS**); /* Get number of free clusters on the drive */ +FRESULT f_truncate (FIL*); /* Truncate file */ FRESULT f_sync (FIL*); /* Flush cached data of a writing file */ FRESULT f_unlink (const char*); /* Delete an existing file or directory */ FRESULT f_mkdir (const char*); /* Create a new directory */ FRESULT f_chmod (const char*, BYTE, BYTE); /* Change file/dir attriburte */ +FRESULT f_utime (const char*, const FILINFO*); /* Change file/dir timestamp */ FRESULT f_rename (const char*, const char*); /* Rename/Move a file or directory */ @@ -273,8 +275,7 @@ DWORD get_fattime (void); /* 31-25: Year(0-127 +1980), 24-21: Month(1-12), 20-16 #define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr)) #define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val) #define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val) -#else -#if _MCU_ENDIAN == 2 /* Use byte-by-byte access */ +#elif _MCU_ENDIAN == 2 /* Use byte-by-byte access */ #define LD_WORD(ptr) (WORD)(((WORD)*(volatile BYTE*)((ptr)+1)<<8)|(WORD)*(volatile BYTE*)(ptr)) #define LD_DWORD(ptr) (DWORD)(((DWORD)*(volatile BYTE*)((ptr)+3)<<24)|((DWORD)*(volatile BYTE*)((ptr)+2)<<16)|((WORD)*(volatile BYTE*)((ptr)+1)<<8)|*(volatile BYTE*)(ptr)) #define ST_WORD(ptr,val) *(volatile BYTE*)(ptr)=(BYTE)(val); *(volatile BYTE*)((ptr)+1)=(BYTE)((WORD)(val)>>8) @@ -282,7 +283,6 @@ DWORD get_fattime (void); /* 31-25: Year(0-127 +1980), 24-21: Month(1-12), 20-16 #else #error Do not forget to set _MCU_ENDIAN properly! #endif -#endif