2019-05-19 15:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-17 02:20:36 +04:00
/*
* linux / fs / fat / misc . c
*
* Written 1992 , 1993 by Werner Almesberger
* 22 / 11 / 2000 - Fixed fat_date_unix2dos for dates earlier than 01 / 01 / 1980
* and date_dos2unix for date = = 0 by Igor Zhbanov ( bsg @ uniyar . ac . ru )
*/
2008-11-06 23:53:46 +03:00
# include "fat.h"
2018-10-31 01:06:53 +03:00
# include <linux/iversion.h>
2005-04-17 02:20:36 +04:00
/*
2009-06-03 21:34:22 +04:00
* fat_fs_error reports a file system problem that might indicate fa data
* corruption / inconsistency . Depending on ' errors ' mount option the
* panic ( ) is called , or error message is printed FAT and nothing is done ,
* or filesystem is remounted read - only ( default behavior ) .
* In case the file system is remounted read - only , it can be made writable
* again by remounting it .
2005-04-17 02:20:36 +04:00
*/
2011-04-12 16:08:38 +04:00
void __fat_fs_error ( struct super_block * sb , int report , const char * fmt , . . . )
2005-04-17 02:20:36 +04:00
{
2011-04-12 16:08:38 +04:00
struct fat_mount_options * opts = & MSDOS_SB ( sb ) - > options ;
2005-04-17 02:20:36 +04:00
va_list args ;
2011-04-12 16:08:38 +04:00
struct va_format vaf ;
2005-04-17 02:20:36 +04:00
2010-05-25 01:33:12 +04:00
if ( report ) {
va_start ( args , fmt ) ;
2011-04-12 16:08:38 +04:00
vaf . fmt = fmt ;
vaf . va = & args ;
2013-07-04 02:08:08 +04:00
fat_msg ( sb , KERN_ERR , " error, %pV " , & vaf ) ;
2010-05-25 01:33:12 +04:00
va_end ( args ) ;
}
2005-04-17 02:20:36 +04:00
2009-06-03 21:34:22 +04:00
if ( opts - > errors = = FAT_ERRORS_PANIC )
2011-04-12 16:08:38 +04:00
panic ( " FAT-fs (%s): fs panic from previous error \n " , sb - > s_id ) ;
2017-07-17 10:45:34 +03:00
else if ( opts - > errors = = FAT_ERRORS_RO & & ! sb_rdonly ( sb ) ) {
2017-11-28 00:05:09 +03:00
sb - > s_flags | = SB_RDONLY ;
2013-07-04 02:08:08 +04:00
fat_msg ( sb , KERN_ERR , " Filesystem has been set read-only " ) ;
2005-04-17 02:20:36 +04:00
}
}
2010-05-25 01:33:12 +04:00
EXPORT_SYMBOL_GPL ( __fat_fs_error ) ;
2005-04-17 02:20:36 +04:00
2011-04-12 16:08:38 +04:00
/**
* fat_msg ( ) - print preformated FAT specific messages . Every thing what is
* not fat_fs_error ( ) should be fat_msg ( ) .
*/
void fat_msg ( struct super_block * sb , const char * level , const char * fmt , . . . )
{
struct va_format vaf ;
va_list args ;
va_start ( args , fmt ) ;
vaf . fmt = fmt ;
vaf . va = & args ;
printk ( " %sFAT-fs (%s): %pV \n " , level , sb - > s_id , & vaf ) ;
va_end ( args ) ;
}
2005-04-17 02:20:36 +04:00
/* Flushes the number of free clusters on FAT32 */
/* XXX: Need to write one per FSINFO block. Currently only writes 1 */
2009-09-19 20:31:58 +04:00
int fat_clusters_flush ( struct super_block * sb )
2005-04-17 02:20:36 +04:00
{
struct msdos_sb_info * sbi = MSDOS_SB ( sb ) ;
struct buffer_head * bh ;
struct fat_boot_fsinfo * fsinfo ;
2019-01-04 02:28:00 +03:00
if ( ! is_fat32 ( sbi ) )
2009-09-19 20:31:58 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
bh = sb_bread ( sb , sbi - > fsinfo_sector ) ;
if ( bh = = NULL ) {
2011-04-12 16:08:38 +04:00
fat_msg ( sb , KERN_ERR , " bread failed in fat_clusters_flush " ) ;
2009-09-19 20:31:58 +04:00
return - EIO ;
2005-04-17 02:20:36 +04:00
}
fsinfo = ( struct fat_boot_fsinfo * ) bh - > b_data ;
/* Sanity check */
if ( ! IS_FSINFO ( fsinfo ) ) {
2011-04-12 16:08:38 +04:00
fat_msg ( sb , KERN_ERR , " Invalid FSINFO signature: "
" 0x%08x, 0x%08x (sector = %lu) " ,
2005-04-17 02:20:36 +04:00
le32_to_cpu ( fsinfo - > signature1 ) ,
le32_to_cpu ( fsinfo - > signature2 ) ,
sbi - > fsinfo_sector ) ;
} else {
if ( sbi - > free_clusters ! = - 1 )
fsinfo - > free_clusters = cpu_to_le32 ( sbi - > free_clusters ) ;
if ( sbi - > prev_free ! = - 1 )
fsinfo - > next_cluster = cpu_to_le32 ( sbi - > prev_free ) ;
mark_buffer_dirty ( bh ) ;
}
brelse ( bh ) ;
2009-09-19 20:31:58 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
* fat_chain_add ( ) adds a new cluster to the chain of clusters represented
* by inode .
*/
int fat_chain_add ( struct inode * inode , int new_dclus , int nr_cluster )
{
struct super_block * sb = inode - > i_sb ;
struct msdos_sb_info * sbi = MSDOS_SB ( sb ) ;
int ret , new_fclus , last ;
/*
* We must locate the last cluster of the file to add this new
* one ( new_dclus ) to the end of the link list ( the FAT ) .
*/
last = new_fclus = 0 ;
if ( MSDOS_I ( inode ) - > i_start ) {
int fclus , dclus ;
ret = fat_get_cluster ( inode , FAT_ENT_EOF , & fclus , & dclus ) ;
if ( ret < 0 )
return ret ;
new_fclus = fclus + 1 ;
last = dclus ;
}
/* add new one to the last of the cluster chain */
if ( last ) {
struct fat_entry fatent ;
fatent_init ( & fatent ) ;
ret = fat_ent_read ( inode , & fatent , last ) ;
if ( ret > = 0 ) {
int wait = inode_needs_sync ( inode ) ;
ret = fat_ent_write ( inode , & fatent , new_dclus , wait ) ;
fatent_brelse ( & fatent ) ;
}
if ( ret < 0 )
return ret ;
2012-12-21 03:05:46 +04:00
/*
* FIXME : Although we can add this cache , fat_cache_add ( ) is
* assuming to be called after linear search with fat_cache_id .
*/
2005-04-17 02:20:36 +04:00
// fat_cache_add(inode, new_fclus, new_dclus);
} else {
MSDOS_I ( inode ) - > i_start = new_dclus ;
MSDOS_I ( inode ) - > i_logstart = new_dclus ;
/*
2009-08-17 19:00:02 +04:00
* Since generic_write_sync ( ) synchronizes regular files later ,
* we sync here only directories .
2005-04-17 02:20:36 +04:00
*/
if ( S_ISDIR ( inode - > i_mode ) & & IS_DIRSYNC ( inode ) ) {
ret = fat_sync_inode ( inode ) ;
if ( ret )
return ret ;
} else
mark_inode_dirty ( inode ) ;
}
if ( new_fclus ! = ( inode - > i_blocks > > ( sbi - > cluster_bits - 9 ) ) ) {
2009-06-03 21:34:22 +04:00
fat_fs_error ( sb , " clusters badly computed (%d != %llu) " ,
2008-11-06 23:53:58 +03:00
new_fclus ,
( llu ) ( inode - > i_blocks > > ( sbi - > cluster_bits - 9 ) ) ) ;
2005-04-17 02:20:36 +04:00
fat_cache_inval_inode ( inode ) ;
}
inode - > i_blocks + = nr_cluster < < ( sbi - > cluster_bits - 9 ) ;
return 0 ;
}
2008-11-06 23:53:47 +03:00
/*
* The epoch of FAT timestamp is 1980.
* : bits : value
* date : 0 - 4 : day ( 1 - 31 )
* date : 5 - 8 : month ( 1 - 12 )
* date : 9 - 15 : year ( 0 - 127 ) from 1980
* time : 0 - 4 : sec ( 0 - 29 ) 2 sec counts
* time : 5 - 10 : min ( 0 - 59 )
* time : 11 - 15 : hour ( 0 - 23 )
*/
# define SECS_PER_MIN 60
# define SECS_PER_HOUR (60 * 60)
# define SECS_PER_DAY (SECS_PER_HOUR * 24)
/* days between 1.1.70 and 1.1.80 (2 leap days) */
# define DAYS_DELTA (365 * 10 + 2)
/* 120 (2100 - 1980) isn't leap year */
# define YEAR_2100 120
# define IS_LEAP_YEAR(y) (!((y) & 3) && (y) != YEAR_2100)
2005-04-17 02:20:36 +04:00
/* Linear day numbers of the respective 1sts in non-leap years. */
2018-08-22 07:59:48 +03:00
static long days_in_year [ ] = {
2008-11-06 23:53:47 +03:00
/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
0 , 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 0 , 0 , 0 ,
2005-04-17 02:20:36 +04:00
} ;
2018-10-31 01:06:50 +03:00
static inline int fat_tz_offset ( struct msdos_sb_info * sbi )
{
return ( sbi - > options . tz_set ?
- sbi - > options . time_offset :
sys_tz . tz_minuteswest ) * SECS_PER_MIN ;
}
2008-11-06 23:53:47 +03:00
/* Convert a FAT time/date pair to a UNIX date (seconds since 1 1 70). */
2018-08-22 07:59:48 +03:00
void fat_time_fat2unix ( struct msdos_sb_info * sbi , struct timespec64 * ts ,
2008-11-06 23:53:47 +03:00
__le16 __time , __le16 __date , u8 time_cs )
2005-04-17 02:20:36 +04:00
{
2008-11-06 23:53:47 +03:00
u16 time = le16_to_cpu ( __time ) , date = le16_to_cpu ( __date ) ;
2018-08-22 07:59:48 +03:00
time64_t second ;
long day , leap_day , month , year ;
2005-04-17 02:20:36 +04:00
2008-11-06 23:53:47 +03:00
year = date > > 9 ;
month = max ( 1 , ( date > > 5 ) & 0xf ) ;
day = max ( 1 , date & 0x1f ) - 1 ;
leap_day = ( year + 3 ) / 4 ;
if ( year > YEAR_2100 ) /* 2100 isn't leap year */
leap_day - - ;
if ( IS_LEAP_YEAR ( year ) & & month > 2 )
leap_day + + ;
second = ( time & 0x1f ) < < 1 ;
second + = ( ( time > > 5 ) & 0x3f ) * SECS_PER_MIN ;
second + = ( time > > 11 ) * SECS_PER_HOUR ;
2018-08-22 07:59:48 +03:00
second + = ( time64_t ) ( year * 365 + leap_day
2008-11-06 23:53:47 +03:00
+ days_in_year [ month ] + day
+ DAYS_DELTA ) * SECS_PER_DAY ;
2018-10-31 01:06:50 +03:00
second + = fat_tz_offset ( sbi ) ;
2008-11-06 23:53:47 +03:00
if ( time_cs ) {
ts - > tv_sec = second + ( time_cs / 100 ) ;
ts - > tv_nsec = ( time_cs % 100 ) * 10000000 ;
} else {
ts - > tv_sec = second ;
ts - > tv_nsec = 0 ;
}
2005-04-17 02:20:36 +04:00
}
2008-11-06 23:53:47 +03:00
/* Convert linear UNIX date to a FAT time/date pair. */
2018-08-22 07:59:48 +03:00
void fat_time_unix2fat ( struct msdos_sb_info * sbi , struct timespec64 * ts ,
2008-11-06 23:53:47 +03:00
__le16 * time , __le16 * date , u8 * time_cs )
2005-04-17 02:20:36 +04:00
{
2009-12-16 03:46:57 +03:00
struct tm tm ;
2018-10-31 01:06:50 +03:00
time64_to_tm ( ts - > tv_sec , - fat_tz_offset ( sbi ) , & tm ) ;
2005-04-17 02:20:36 +04:00
2009-12-16 03:46:57 +03:00
/* FAT can only support year between 1980 to 2107 */
if ( tm . tm_year < 1980 - 1900 ) {
2008-11-06 23:53:47 +03:00
* time = 0 ;
* date = cpu_to_le16 ( ( 0 < < 9 ) | ( 1 < < 5 ) | 1 ) ;
if ( time_cs )
* time_cs = 0 ;
return ;
}
2009-12-16 03:46:57 +03:00
if ( tm . tm_year > 2107 - 1900 ) {
2008-11-06 23:53:47 +03:00
* time = cpu_to_le16 ( ( 23 < < 11 ) | ( 59 < < 5 ) | 29 ) ;
* date = cpu_to_le16 ( ( 127 < < 9 ) | ( 12 < < 5 ) | 31 ) ;
if ( time_cs )
* time_cs = 199 ;
return ;
}
2009-12-16 03:46:57 +03:00
/* from 1900 -> from 1980 */
tm . tm_year - = 80 ;
/* 0~11 -> 1~12 */
tm . tm_mon + + ;
/* 0~59 -> 0~29(2sec counts) */
tm . tm_sec > > = 1 ;
2005-04-17 02:20:36 +04:00
2009-12-16 03:46:57 +03:00
* time = cpu_to_le16 ( tm . tm_hour < < 11 | tm . tm_min < < 5 | tm . tm_sec ) ;
* date = cpu_to_le16 ( tm . tm_year < < 9 | tm . tm_mon < < 5 | tm . tm_mday ) ;
2008-11-06 23:53:47 +03:00
if ( time_cs )
* time_cs = ( ts - > tv_sec & 1 ) * 100 + ts - > tv_nsec / 10000000 ;
}
EXPORT_SYMBOL_GPL ( fat_time_unix2fat ) ;
2005-04-17 02:20:36 +04:00
2018-10-31 01:06:53 +03:00
static inline struct timespec64 fat_timespec64_trunc_2secs ( struct timespec64 ts )
{
return ( struct timespec64 ) { ts . tv_sec & ~ 1ULL , 0 } ;
}
/*
* truncate the various times with appropriate granularity :
* root inode :
* all times always 0
* all other inodes :
* mtime - 2 seconds
* ctime
* msdos - 2 seconds
* vfat - 10 milliseconds
* atime - 24 hours ( 00 : 00 : 00 in local timezone )
*/
int fat_truncate_time ( struct inode * inode , struct timespec64 * now , int flags )
{
struct msdos_sb_info * sbi = MSDOS_SB ( inode - > i_sb ) ;
struct timespec64 ts ;
if ( inode - > i_ino = = MSDOS_ROOT_INO )
return 0 ;
if ( now = = NULL ) {
now = & ts ;
ts = current_time ( inode ) ;
}
if ( flags & S_ATIME ) {
/* to localtime */
time64_t seconds = now - > tv_sec - fat_tz_offset ( sbi ) ;
s32 remainder ;
div_s64_rem ( seconds , SECS_PER_DAY , & remainder ) ;
/* to day boundary, and back to unix time */
seconds = seconds + fat_tz_offset ( sbi ) - remainder ;
inode - > i_atime = ( struct timespec64 ) { seconds , 0 } ;
}
if ( flags & S_CTIME ) {
if ( sbi - > options . isvfat )
inode - > i_ctime = timespec64_trunc ( * now , 10000000 ) ;
else
inode - > i_ctime = fat_timespec64_trunc_2secs ( * now ) ;
}
if ( flags & S_MTIME )
inode - > i_mtime = fat_timespec64_trunc_2secs ( * now ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( fat_truncate_time ) ;
int fat_update_time ( struct inode * inode , struct timespec64 * now , int flags )
{
int iflags = I_DIRTY_TIME ;
bool dirty = false ;
if ( inode - > i_ino = = MSDOS_ROOT_INO )
return 0 ;
fat_truncate_time ( inode , now , flags ) ;
if ( flags & S_VERSION )
dirty = inode_maybe_inc_iversion ( inode , false ) ;
if ( ( flags & ( S_ATIME | S_CTIME | S_MTIME ) ) & &
! ( inode - > i_sb - > s_flags & SB_LAZYTIME ) )
dirty = true ;
if ( dirty )
iflags | = I_DIRTY_SYNC ;
__mark_inode_dirty ( inode , iflags ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( fat_update_time ) ;
2005-04-17 02:20:36 +04:00
int fat_sync_bhs ( struct buffer_head * * bhs , int nr_bhs )
{
2006-02-03 14:04:42 +03:00
int i , err = 0 ;
2005-04-17 02:20:36 +04:00
2010-08-11 19:06:24 +04:00
for ( i = 0 ; i < nr_bhs ; i + + )
2016-06-05 22:31:43 +03:00
write_dirty_buffer ( bhs [ i ] , 0 ) ;
2010-08-11 19:06:24 +04:00
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < nr_bhs ; i + + ) {
wait_on_buffer ( bhs [ i ] ) ;
2010-08-18 13:29:23 +04:00
if ( ! err & & ! buffer_uptodate ( bhs [ i ] ) )
2005-04-17 02:20:36 +04:00
err = - EIO ;
}
return err ;
}