Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
CONFIG_KUNIT=y
CONFIG_FAT_FS=y
CONFIG_MSDOS_FS=y
CONFIG_VFAT_FS=y
CONFIG_FAT_KUNIT_TEST=y
+131
View File
@@ -0,0 +1,131 @@
# SPDX-License-Identifier: GPL-2.0-only
config FAT_FS
tristate
select BUFFER_HEAD
select NLS
select LEGACY_DIRECT_IO
help
If you want to use one of the FAT-based file systems (the MS-DOS and
VFAT (Windows 95) file systems), then you must say Y or M here
to include FAT support. You will then be able to mount partitions or
diskettes with FAT-based file systems and transparently access the
files on them, i.e. MSDOS files will look and behave just like all
other Unix files.
This FAT support is not a file system in itself, it only provides
the foundation for the other file systems. You will have to say Y or
M to at least one of "MSDOS fs support" or "VFAT fs support" in
order to make use of it.
Another way to read and write MSDOS floppies and hard drive
partitions from within Linux (but not transparently) is with the
mtools ("man mtools") program suite. You don't need to say Y here in
order to do that.
If you need to move large files on floppies between a DOS and a
Linux box, say Y here, mount the floppy under Linux with an MSDOS
file system and use GNU tar's M option. GNU tar is a program
available for Unix and DOS ("man tar" or "info tar").
The FAT support will enlarge your kernel by about 37 KB. If unsure,
say Y.
To compile this as a module, choose M here: the module will be called
fat. Note that if you compile the FAT support as a module, you
cannot compile any of the FAT-based file systems into the kernel
-- they will have to be modules as well.
config MSDOS_FS
tristate "MSDOS fs support"
select FAT_FS
help
This allows you to mount MSDOS partitions of your hard drive (unless
they are compressed; to access compressed MSDOS partitions under
Linux, you can either use the DOS emulator DOSEMU, described in the
DOSEMU-HOWTO, available from
<https://www.tldp.org/docs.html#howto>, or try dmsdosfs in
<ftp://ibiblio.org/pub/Linux/system/filesystems/dosfs/>. If you
intend to use dosemu with a non-compressed MSDOS partition, say Y
here) and MSDOS floppies. This means that file access becomes
transparent, i.e. the MSDOS files look and behave just like all
other Unix files.
If you have Windows 95 or Windows NT installed on your MSDOS
partitions, you should use the VFAT file system (say Y to "VFAT fs
support" below), or you will not be able to see the long filenames
generated by Windows 95 / Windows NT.
This option will enlarge your kernel by about 7 KB. If unsure,
answer Y. This will only work if you said Y to "DOS FAT fs support"
as well. To compile this as a module, choose M here: the module will
be called msdos.
config VFAT_FS
tristate "VFAT (Windows-95) fs support"
select FAT_FS
help
This option provides support for normal Windows file systems with
long filenames. That includes non-compressed FAT-based file systems
used by Windows 95, Windows 98, Windows NT 4.0, and the Unix
programs from the mtools package.
The VFAT support enlarges your kernel by about 10 KB and it only
works if you said Y to the "DOS FAT fs support" above. Please read
the file <file:Documentation/filesystems/vfat.rst> for details. If
unsure, say Y.
To compile this as a module, choose M here: the module will be called
vfat.
config FAT_DEFAULT_CODEPAGE
int "Default codepage for FAT"
depends on FAT_FS
default 437
help
This option should be set to the codepage of your FAT filesystems.
It can be overridden with the "codepage" mount option.
See <file:Documentation/filesystems/vfat.rst> for more information.
config FAT_DEFAULT_IOCHARSET
string "Default iocharset for FAT"
depends on VFAT_FS
default "iso8859-1"
help
Set this to the default input/output character set you'd
like FAT to use. It should probably match the character set
that most of your FAT filesystems use, and can be overridden
with the "iocharset" mount option for FAT filesystems.
Note that "utf8" is not recommended for FAT filesystems.
If unsure, you shouldn't set "utf8" here - select the next option
instead if you would like to use UTF-8 encoded file names by default.
See <file:Documentation/filesystems/vfat.rst> for more information.
Enable any character sets you need in File Systems/Native Language
Support.
config FAT_DEFAULT_UTF8
bool "Enable FAT UTF-8 option by default"
depends on VFAT_FS
default n
help
Set this if you would like to have "utf8" mount option set
by default when mounting FAT filesystems.
Even if you say Y here can always disable UTF-8 for
particular mount by adding "utf8=0" to mount options.
Say Y if you use UTF-8 encoding for file names, N otherwise.
See <file:Documentation/filesystems/vfat.rst> for more information.
config FAT_KUNIT_TEST
tristate "Unit Tests for FAT filesystems" if !KUNIT_ALL_TESTS
depends on KUNIT && FAT_FS
default KUNIT_ALL_TESTS
help
This builds the FAT KUnit tests
For more information on KUnit and unit tests in general, please refer
to the KUnit documentation in Documentation/dev-tools/kunit
If unsure, say N
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Linux fat filesystem support.
#
obj-$(CONFIG_FAT_FS) += fat.o
obj-$(CONFIG_VFAT_FS) += vfat.o
obj-$(CONFIG_MSDOS_FS) += msdos.o
fat-y := cache.o dir.o fatent.o file.o inode.o misc.o nfs.o
vfat-y := namei_vfat.o
msdos-y := namei_msdos.o
obj-$(CONFIG_FAT_KUNIT_TEST) += fat_test.o
+381
View File
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/fat/cache.c
*
* Written 1992,1993 by Werner Almesberger
*
* Mar 1999. AV. Changed cache, so that it uses the starting cluster instead
* of inode number.
* May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers.
*/
#include <linux/slab.h>
#include "fat.h"
/* this must be > 0. */
#define FAT_MAX_CACHE 8
struct fat_cache {
struct list_head cache_list;
int nr_contig; /* number of contiguous clusters */
int fcluster; /* cluster number in the file. */
int dcluster; /* cluster number on disk. */
};
struct fat_cache_id {
unsigned int id;
int nr_contig;
int fcluster;
int dcluster;
};
static struct kmem_cache *fat_cache_cachep;
static void init_once(void *foo)
{
struct fat_cache *cache = (struct fat_cache *)foo;
INIT_LIST_HEAD(&cache->cache_list);
}
int __init fat_cache_init(void)
{
fat_cache_cachep = kmem_cache_create("fat_cache",
sizeof(struct fat_cache),
0, SLAB_RECLAIM_ACCOUNT,
init_once);
if (fat_cache_cachep == NULL)
return -ENOMEM;
return 0;
}
void fat_cache_destroy(void)
{
kmem_cache_destroy(fat_cache_cachep);
}
static inline struct fat_cache *fat_cache_alloc(void)
{
return kmem_cache_alloc(fat_cache_cachep, GFP_NOFS);
}
static inline void fat_cache_free(struct fat_cache *cache)
{
BUG_ON(!list_empty(&cache->cache_list));
kmem_cache_free(fat_cache_cachep, cache);
}
static inline void fat_cache_update_lru(struct inode *inode,
struct fat_cache *cache)
{
if (MSDOS_I(inode)->cache_lru.next != &cache->cache_list)
list_move(&cache->cache_list, &MSDOS_I(inode)->cache_lru);
}
static int fat_cache_lookup(struct inode *inode, int fclus,
struct fat_cache_id *cid,
int *cached_fclus, int *cached_dclus)
{
static struct fat_cache nohit = { .fcluster = 0, };
struct fat_cache *hit = &nohit, *p;
int offset = -1;
spin_lock(&MSDOS_I(inode)->cache_lru_lock);
list_for_each_entry(p, &MSDOS_I(inode)->cache_lru, cache_list) {
/* Find the cache of "fclus" or nearest cache. */
if (p->fcluster <= fclus && hit->fcluster < p->fcluster) {
hit = p;
if ((hit->fcluster + hit->nr_contig) < fclus) {
offset = hit->nr_contig;
} else {
offset = fclus - hit->fcluster;
break;
}
}
}
if (hit != &nohit) {
fat_cache_update_lru(inode, hit);
cid->id = MSDOS_I(inode)->cache_valid_id;
cid->nr_contig = hit->nr_contig;
cid->fcluster = hit->fcluster;
cid->dcluster = hit->dcluster;
*cached_fclus = cid->fcluster + offset;
*cached_dclus = cid->dcluster + offset;
}
spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
return offset;
}
static struct fat_cache *fat_cache_merge(struct inode *inode,
struct fat_cache_id *new)
{
struct fat_cache *p;
list_for_each_entry(p, &MSDOS_I(inode)->cache_lru, cache_list) {
/* Find the same part as "new" in cluster-chain. */
if (p->fcluster == new->fcluster) {
BUG_ON(p->dcluster != new->dcluster);
if (new->nr_contig > p->nr_contig)
p->nr_contig = new->nr_contig;
return p;
}
}
return NULL;
}
static void fat_cache_add(struct inode *inode, struct fat_cache_id *new)
{
struct fat_cache *cache, *tmp;
if (new->fcluster == -1) /* dummy cache */
return;
spin_lock(&MSDOS_I(inode)->cache_lru_lock);
if (new->id != FAT_CACHE_VALID &&
new->id != MSDOS_I(inode)->cache_valid_id)
goto out; /* this cache was invalidated */
cache = fat_cache_merge(inode, new);
if (cache == NULL) {
if (MSDOS_I(inode)->nr_caches < FAT_MAX_CACHE) {
MSDOS_I(inode)->nr_caches++;
spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
tmp = fat_cache_alloc();
if (!tmp) {
spin_lock(&MSDOS_I(inode)->cache_lru_lock);
MSDOS_I(inode)->nr_caches--;
spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
return;
}
spin_lock(&MSDOS_I(inode)->cache_lru_lock);
cache = fat_cache_merge(inode, new);
if (cache != NULL) {
MSDOS_I(inode)->nr_caches--;
fat_cache_free(tmp);
goto out_update_lru;
}
cache = tmp;
} else {
struct list_head *p = MSDOS_I(inode)->cache_lru.prev;
cache = list_entry(p, struct fat_cache, cache_list);
}
cache->fcluster = new->fcluster;
cache->dcluster = new->dcluster;
cache->nr_contig = new->nr_contig;
}
out_update_lru:
fat_cache_update_lru(inode, cache);
out:
spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
}
/*
* Cache invalidation occurs rarely, thus the LRU chain is not updated. It
* fixes itself after a while.
*/
static void __fat_cache_inval_inode(struct inode *inode)
{
struct msdos_inode_info *i = MSDOS_I(inode);
struct fat_cache *cache;
while (!list_empty(&i->cache_lru)) {
cache = list_entry(i->cache_lru.next,
struct fat_cache, cache_list);
list_del_init(&cache->cache_list);
i->nr_caches--;
fat_cache_free(cache);
}
/* Update. The copy of caches before this id is discarded. */
i->cache_valid_id++;
if (i->cache_valid_id == FAT_CACHE_VALID)
i->cache_valid_id++;
}
void fat_cache_inval_inode(struct inode *inode)
{
spin_lock(&MSDOS_I(inode)->cache_lru_lock);
__fat_cache_inval_inode(inode);
spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
}
static inline int cache_contiguous(struct fat_cache_id *cid, int dclus)
{
cid->nr_contig++;
return ((cid->dcluster + cid->nr_contig) == dclus);
}
static inline void cache_init(struct fat_cache_id *cid, int fclus, int dclus)
{
cid->id = FAT_CACHE_VALID;
cid->fcluster = fclus;
cid->dcluster = dclus;
cid->nr_contig = 0;
}
int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const int limit = sb->s_maxbytes >> sbi->cluster_bits;
struct fat_entry fatent;
struct fat_cache_id cid;
int nr;
BUG_ON(MSDOS_I(inode)->i_start == 0);
*fclus = 0;
*dclus = MSDOS_I(inode)->i_start;
if (!fat_valid_entry(sbi, *dclus)) {
fat_fs_error_ratelimit(sb,
"%s: invalid start cluster (i_pos %lld, start %08x)",
__func__, MSDOS_I(inode)->i_pos, *dclus);
return -EIO;
}
if (cluster == 0)
return 0;
if (fat_cache_lookup(inode, cluster, &cid, fclus, dclus) < 0) {
/*
* dummy, always not contiguous
* This is reinitialized by cache_init(), later.
*/
cache_init(&cid, -1, -1);
}
fatent_init(&fatent);
while (*fclus < cluster) {
/* prevent the infinite loop of cluster chain */
if (*fclus > limit) {
fat_fs_error_ratelimit(sb,
"%s: detected the cluster chain loop (i_pos %lld)",
__func__, MSDOS_I(inode)->i_pos);
nr = -EIO;
goto out;
}
nr = fat_ent_read(inode, &fatent, *dclus);
if (nr < 0)
goto out;
else if (nr == FAT_ENT_FREE) {
fat_fs_error_ratelimit(sb,
"%s: invalid cluster chain (i_pos %lld)",
__func__, MSDOS_I(inode)->i_pos);
nr = -EIO;
goto out;
} else if (nr == FAT_ENT_EOF) {
fat_cache_add(inode, &cid);
goto out;
}
(*fclus)++;
*dclus = nr;
if (!cache_contiguous(&cid, *dclus))
cache_init(&cid, *fclus, *dclus);
}
nr = 0;
fat_cache_add(inode, &cid);
out:
fatent_brelse(&fatent);
return nr;
}
static int fat_bmap_cluster(struct inode *inode, int cluster)
{
struct super_block *sb = inode->i_sb;
int ret, fclus, dclus;
if (MSDOS_I(inode)->i_start == 0)
return 0;
ret = fat_get_cluster(inode, cluster, &fclus, &dclus);
if (ret < 0)
return ret;
else if (ret == FAT_ENT_EOF) {
fat_fs_error(sb, "%s: request beyond EOF (i_pos %lld)",
__func__, MSDOS_I(inode)->i_pos);
return -EIO;
}
return dclus;
}
int fat_get_mapped_cluster(struct inode *inode, sector_t sector,
sector_t last_block,
unsigned long *mapped_blocks, sector_t *bmap)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int cluster, offset;
cluster = sector >> (sbi->cluster_bits - sb->s_blocksize_bits);
offset = sector & (sbi->sec_per_clus - 1);
cluster = fat_bmap_cluster(inode, cluster);
if (cluster < 0)
return cluster;
else if (cluster) {
*bmap = fat_clus_to_blknr(sbi, cluster) + offset;
*mapped_blocks = sbi->sec_per_clus - offset;
if (*mapped_blocks > last_block - sector)
*mapped_blocks = last_block - sector;
}
return 0;
}
static int is_exceed_eof(struct inode *inode, sector_t sector,
sector_t *last_block, int create)
{
struct super_block *sb = inode->i_sb;
const unsigned long blocksize = sb->s_blocksize;
const unsigned char blocksize_bits = sb->s_blocksize_bits;
*last_block = (i_size_read(inode) + (blocksize - 1)) >> blocksize_bits;
if (sector >= *last_block) {
if (!create)
return 1;
/*
* ->mmu_private can access on only allocation path.
* (caller must hold ->i_mutex)
*/
*last_block = (MSDOS_I(inode)->mmu_private + (blocksize - 1))
>> blocksize_bits;
if (sector >= *last_block)
return 1;
}
return 0;
}
int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys,
unsigned long *mapped_blocks, int create, bool from_bmap)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
sector_t last_block;
*phys = 0;
*mapped_blocks = 0;
if (!is_fat32(sbi) && (inode->i_ino == MSDOS_ROOT_INO)) {
if (sector < (sbi->dir_entries >> sbi->dir_per_block_bits)) {
*phys = sector + sbi->dir_start;
*mapped_blocks = 1;
}
return 0;
}
if (!from_bmap) {
if (is_exceed_eof(inode, sector, &last_block, create))
return 0;
} else {
last_block = inode->i_blocks >>
(inode->i_sb->s_blocksize_bits - 9);
if (sector >= last_block)
return 0;
}
return fat_get_mapped_cluster(inode, sector, last_block, mapped_blocks,
phys);
}
File diff suppressed because it is too large Load Diff
+493
View File
@@ -0,0 +1,493 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FAT_H
#define _FAT_H
#include <linux/buffer_head.h>
#include <linux/nls.h>
#include <linux/hash.h>
#include <linux/ratelimit.h>
#include <linux/msdos_fs.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
struct file_kattr;
/*
* vfat shortname flags
*/
#define VFAT_SFN_DISPLAY_LOWER 0x0001 /* convert to lowercase for display */
#define VFAT_SFN_DISPLAY_WIN95 0x0002 /* emulate win95 rule for display */
#define VFAT_SFN_DISPLAY_WINNT 0x0004 /* emulate winnt rule for display */
#define VFAT_SFN_CREATE_WIN95 0x0100 /* emulate win95 rule for create */
#define VFAT_SFN_CREATE_WINNT 0x0200 /* emulate winnt rule for create */
#define FAT_ERRORS_CONT 1 /* ignore error and continue */
#define FAT_ERRORS_PANIC 2 /* panic on error */
#define FAT_ERRORS_RO 3 /* remount r/o on error */
#define FAT_NFS_STALE_RW 1 /* NFS RW support, can cause ESTALE */
#define FAT_NFS_NOSTALE_RO 2 /* NFS RO support, no ESTALE issue */
struct fat_mount_options {
kuid_t fs_uid;
kgid_t fs_gid;
unsigned short fs_fmask;
unsigned short fs_dmask;
unsigned short codepage; /* Codepage for shortname conversions */
int time_offset; /* Offset of timestamps from UTC (in minutes) */
char *iocharset; /* Charset used for filename input/display */
unsigned short shortname; /* flags for shortname display/create rule */
unsigned char name_check; /* r = relaxed, n = normal, s = strict */
unsigned char errors; /* On error: continue, panic, remount-ro */
unsigned char nfs; /* NFS support: nostale_ro, stale_rw */
unsigned short allow_utime;/* permission for setting the [am]time */
unsigned quiet:1, /* set = fake successful chmods and chowns */
showexec:1, /* set = only set x bit for com/exe/bat */
sys_immutable:1, /* set = system files are immutable */
dotsOK:1, /* set = hidden and system files are named '.filename' */
isvfat:1, /* 0=no vfat long filename support, 1=vfat support */
utf8:1, /* Use of UTF-8 character set (Default) */
unicode_xlate:1, /* create escape sequences for unhandled Unicode */
numtail:1, /* Does first alias have a numeric '~1' type tail? */
flush:1, /* write things quickly */
nocase:1, /* Does this need case conversion? 0=need case conversion*/
usefree:1, /* Use free_clusters for FAT32 */
tz_set:1, /* Filesystem timestamps' offset set */
rodir:1, /* allow ATTR_RO for directory */
discard:1, /* Issue discard requests on deletions */
dos1xfloppy:1, /* Assume default BPB for DOS 1.x floppies */
debug:1; /* Not currently used */
};
#define FAT_HASH_BITS 8
#define FAT_HASH_SIZE (1UL << FAT_HASH_BITS)
/*
* MS-DOS file system in-core superblock data
*/
struct msdos_sb_info {
unsigned short sec_per_clus; /* sectors/cluster */
unsigned short cluster_bits; /* log2(cluster_size) */
unsigned int cluster_size; /* cluster size */
unsigned char fats, fat_bits; /* number of FATs, FAT bits (12,16 or 32) */
unsigned short fat_start;
unsigned long fat_length; /* FAT start & length (sec.) */
unsigned long dir_start;
unsigned short dir_entries; /* root dir start & entries */
unsigned long data_start; /* first data sector */
unsigned long max_cluster; /* maximum cluster number */
unsigned long root_cluster; /* first cluster of the root directory */
unsigned long fsinfo_sector; /* sector number of FAT32 fsinfo */
struct mutex fat_lock;
struct mutex nfs_build_inode_lock;
struct mutex s_lock;
unsigned int prev_free; /* previously allocated cluster number */
unsigned int free_clusters; /* -1 if undefined */
unsigned int free_clus_valid; /* is free_clusters valid? */
struct fat_mount_options options;
struct nls_table *nls_disk; /* Codepage used on disk */
struct nls_table *nls_io; /* Charset used for input and display */
const void *dir_ops; /* Opaque; default directory operations */
int dir_per_block; /* dir entries per block */
int dir_per_block_bits; /* log2(dir_per_block) */
unsigned int vol_id; /*volume ID*/
int fatent_shift;
const struct fatent_operations *fatent_ops;
struct inode *fat_inode;
struct inode *fsinfo_inode;
struct ratelimit_state ratelimit;
spinlock_t inode_hash_lock;
struct hlist_head inode_hashtable[FAT_HASH_SIZE];
spinlock_t dir_hash_lock;
struct hlist_head dir_hashtable[FAT_HASH_SIZE];
unsigned int dirty; /* fs state before mount */
struct rcu_head rcu;
};
#define FAT_CACHE_VALID 0 /* special case for valid cache */
/*
* MS-DOS file system inode data in memory
*/
struct msdos_inode_info {
spinlock_t cache_lru_lock;
struct list_head cache_lru;
int nr_caches;
/* for avoiding the race between fat_free() and fat_get_cluster() */
unsigned int cache_valid_id;
/* NOTE: mmu_private is 64bits, so must hold ->i_mutex to access */
loff_t mmu_private; /* physically allocated size */
int i_start; /* first cluster or 0 */
int i_logstart; /* logical first cluster */
int i_attrs; /* unused attribute bits */
loff_t i_pos; /* on-disk position of directory entry or 0 */
struct hlist_node i_fat_hash; /* hash by i_location */
struct hlist_node i_dir_hash; /* hash by i_logstart */
struct rw_semaphore truncate_lock; /* protect bmap against truncate */
struct timespec64 i_crtime; /* File creation (birth) time */
struct mapping_metadata_bhs i_metadata_bhs;
struct inode vfs_inode;
};
struct fat_slot_info {
loff_t i_pos; /* on-disk position of directory entry */
loff_t slot_off; /* offset for slot or de start */
int nr_slots; /* number of slots + 1(de) in filename */
struct msdos_dir_entry *de;
struct buffer_head *bh;
};
static inline struct msdos_sb_info *MSDOS_SB(struct super_block *sb)
{
return sb->s_fs_info;
}
/*
* Functions that determine the variant of the FAT file system (i.e.,
* whether this is FAT12, FAT16 or FAT32.
*/
static inline bool is_fat12(const struct msdos_sb_info *sbi)
{
return sbi->fat_bits == 12;
}
static inline bool is_fat16(const struct msdos_sb_info *sbi)
{
return sbi->fat_bits == 16;
}
static inline bool is_fat32(const struct msdos_sb_info *sbi)
{
return sbi->fat_bits == 32;
}
/* Maximum number of clusters */
static inline u32 max_fat(struct super_block *sb)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
return is_fat32(sbi) ? MAX_FAT32 :
is_fat16(sbi) ? MAX_FAT16 : MAX_FAT12;
}
static inline struct msdos_inode_info *MSDOS_I(struct inode *inode)
{
return container_of(inode, struct msdos_inode_info, vfs_inode);
}
/*
* If ->i_mode can't hold S_IWUGO (i.e. ATTR_RO), we use ->i_attrs to
* save ATTR_RO instead of ->i_mode.
*
* If it's directory and !sbi->options.rodir, ATTR_RO isn't read-only
* bit, it's just used as flag for app.
*/
static inline int fat_mode_can_hold_ro(struct inode *inode)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
umode_t mask;
if (S_ISDIR(inode->i_mode)) {
if (!sbi->options.rodir)
return 0;
mask = ~sbi->options.fs_dmask;
} else
mask = ~sbi->options.fs_fmask;
if (!(mask & S_IWUGO))
return 0;
return 1;
}
/* Convert attribute bits and a mask to the UNIX mode. */
static inline umode_t fat_make_mode(struct msdos_sb_info *sbi,
u8 attrs, umode_t mode)
{
if (attrs & ATTR_RO && !((attrs & ATTR_DIR) && !sbi->options.rodir))
mode &= ~S_IWUGO;
if (attrs & ATTR_DIR)
return (mode & ~sbi->options.fs_dmask) | S_IFDIR;
else
return (mode & ~sbi->options.fs_fmask) | S_IFREG;
}
/* Return the FAT attribute byte for this inode */
static inline u8 fat_make_attrs(struct inode *inode)
{
u8 attrs = MSDOS_I(inode)->i_attrs;
if (S_ISDIR(inode->i_mode))
attrs |= ATTR_DIR;
if (fat_mode_can_hold_ro(inode) && !(inode->i_mode & S_IWUGO))
attrs |= ATTR_RO;
return attrs;
}
static inline void fat_save_attrs(struct inode *inode, u8 attrs)
{
if (fat_mode_can_hold_ro(inode))
MSDOS_I(inode)->i_attrs = attrs & ATTR_UNUSED;
else
MSDOS_I(inode)->i_attrs = attrs & (ATTR_UNUSED | ATTR_RO);
}
static inline unsigned char fat_checksum(const __u8 *name)
{
unsigned char s = name[0];
s = (s<<7) + (s>>1) + name[1]; s = (s<<7) + (s>>1) + name[2];
s = (s<<7) + (s>>1) + name[3]; s = (s<<7) + (s>>1) + name[4];
s = (s<<7) + (s>>1) + name[5]; s = (s<<7) + (s>>1) + name[6];
s = (s<<7) + (s>>1) + name[7]; s = (s<<7) + (s>>1) + name[8];
s = (s<<7) + (s>>1) + name[9]; s = (s<<7) + (s>>1) + name[10];
return s;
}
static inline sector_t fat_clus_to_blknr(const struct msdos_sb_info *sbi, int clus)
{
return ((sector_t)clus - FAT_START_ENT) * sbi->sec_per_clus
+ sbi->data_start;
}
static inline void fat_get_blknr_offset(const struct msdos_sb_info *sbi,
loff_t i_pos, sector_t *blknr, int *offset)
{
*blknr = i_pos >> sbi->dir_per_block_bits;
*offset = i_pos & (sbi->dir_per_block - 1);
}
static inline loff_t fat_i_pos_read(struct msdos_sb_info *sbi,
struct inode *inode)
{
loff_t i_pos;
#if BITS_PER_LONG == 32
spin_lock(&sbi->inode_hash_lock);
#endif
i_pos = MSDOS_I(inode)->i_pos;
#if BITS_PER_LONG == 32
spin_unlock(&sbi->inode_hash_lock);
#endif
return i_pos;
}
static inline void fat16_towchar(wchar_t *dst, const __u8 *src, size_t len)
{
#ifdef __BIG_ENDIAN
while (len--) {
*dst++ = src[0] | (src[1] << 8);
src += 2;
}
#else
memcpy(dst, src, len * 2);
#endif
}
static inline int fat_get_start(const struct msdos_sb_info *sbi,
const struct msdos_dir_entry *de)
{
int cluster = le16_to_cpu(de->start);
if (is_fat32(sbi))
cluster |= (le16_to_cpu(de->starthi) << 16);
return cluster;
}
static inline void fat_set_start(struct msdos_dir_entry *de, int cluster)
{
de->start = cpu_to_le16(cluster);
de->starthi = cpu_to_le16(cluster >> 16);
}
static inline void fatwchar_to16(__u8 *dst, const wchar_t *src, size_t len)
{
#ifdef __BIG_ENDIAN
while (len--) {
dst[0] = *src & 0x00FF;
dst[1] = (*src & 0xFF00) >> 8;
dst += 2;
src++;
}
#else
memcpy(dst, src, len * 2);
#endif
}
/* fat/cache.c */
extern void fat_cache_inval_inode(struct inode *inode);
extern int fat_get_cluster(struct inode *inode, int cluster,
int *fclus, int *dclus);
extern int fat_get_mapped_cluster(struct inode *inode, sector_t sector,
sector_t last_block,
unsigned long *mapped_blocks, sector_t *bmap);
extern int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys,
unsigned long *mapped_blocks, int create, bool from_bmap);
/* fat/dir.c */
extern const struct file_operations fat_dir_operations;
extern int fat_search_long(struct inode *inode, const unsigned char *name,
int name_len, struct fat_slot_info *sinfo);
extern int fat_dir_empty(struct inode *dir);
extern int fat_subdirs(struct inode *dir);
extern int fat_scan(struct inode *dir, const unsigned char *name,
struct fat_slot_info *sinfo);
extern int fat_scan_logstart(struct inode *dir, int i_logstart,
struct fat_slot_info *sinfo);
extern int fat_get_dotdot_entry(struct inode *dir, struct buffer_head **bh,
struct msdos_dir_entry **de);
extern int fat_alloc_new_dir(struct inode *dir, struct timespec64 *ts);
extern int fat_add_entries(struct inode *dir, void *slots, int nr_slots,
struct fat_slot_info *sinfo);
extern int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo);
/* fat/fatent.c */
struct fat_entry {
int entry;
union {
u8 *ent12_p[2];
__le16 *ent16_p;
__le32 *ent32_p;
} u;
int nr_bhs;
struct buffer_head *bhs[2];
struct inode *fat_inode;
};
static inline void fatent_init(struct fat_entry *fatent)
{
fatent->nr_bhs = 0;
fatent->entry = 0;
fatent->u.ent32_p = NULL;
fatent->bhs[0] = fatent->bhs[1] = NULL;
fatent->fat_inode = NULL;
}
static inline void fatent_set_entry(struct fat_entry *fatent, int entry)
{
fatent->entry = entry;
fatent->u.ent32_p = NULL;
}
static inline void fatent_brelse(struct fat_entry *fatent)
{
int i;
fatent->u.ent32_p = NULL;
for (i = 0; i < fatent->nr_bhs; i++)
brelse(fatent->bhs[i]);
fatent->nr_bhs = 0;
fatent->bhs[0] = fatent->bhs[1] = NULL;
fatent->fat_inode = NULL;
}
static inline bool fat_valid_entry(struct msdos_sb_info *sbi, int entry)
{
return FAT_START_ENT <= entry && entry < sbi->max_cluster;
}
extern void fat_ent_access_init(struct super_block *sb);
extern int fat_ent_read(struct inode *inode, struct fat_entry *fatent,
int entry);
extern int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
int new, int wait);
extern int fat_alloc_clusters(struct inode *inode, int *cluster,
int nr_cluster);
extern int fat_free_clusters(struct inode *inode, int cluster);
extern int fat_count_free_clusters(struct super_block *sb);
extern int fat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* fat/file.c */
extern long fat_generic_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg);
extern const struct file_operations fat_file_operations;
extern const struct inode_operations fat_file_inode_operations;
extern int fat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
extern void fat_truncate_blocks(struct inode *inode, loff_t offset);
extern int fat_getattr(struct mnt_idmap *idmap,
const struct path *path, struct kstat *stat,
u32 request_mask, unsigned int flags);
int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
extern int fat_file_fsync(struct file *file, loff_t start, loff_t end,
int datasync);
/* fat/inode.c */
extern int fat_block_truncate_page(struct inode *inode, loff_t from);
extern void fat_attach(struct inode *inode, loff_t i_pos);
extern void fat_detach(struct inode *inode);
extern struct inode *fat_iget(struct super_block *sb, loff_t i_pos);
extern struct inode *fat_build_inode(struct super_block *sb,
struct msdos_dir_entry *de, loff_t i_pos);
extern int fat_sync_inode(struct inode *inode);
extern int fat_fill_super(struct super_block *sb, struct fs_context *fc,
void (*setup)(struct super_block *));
extern int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de);
extern int fat_flush_inodes(struct super_block *sb, struct inode *i1,
struct inode *i2);
extern const struct fs_parameter_spec fat_param_spec[];
int fat_init_fs_context(struct fs_context *fc, bool is_vfat);
void fat_free_fc(struct fs_context *fc);
int fat_parse_param(struct fs_context *fc, struct fs_parameter *param,
bool is_vfat);
int fat_reconfigure(struct fs_context *fc);
static inline unsigned long fat_dir_hash(int logstart)
{
return hash_32(logstart, FAT_HASH_BITS);
}
extern int fat_add_cluster(struct inode *inode);
/* fat/misc.c */
extern __printf(3, 4) __cold
void __fat_fs_error(struct super_block *sb, int report, const char *fmt, ...);
#define fat_fs_error(sb, fmt, args...) \
__fat_fs_error(sb, 1, fmt , ## args)
#define fat_fs_error_ratelimit(sb, fmt, args...) \
__fat_fs_error(sb, __ratelimit(&MSDOS_SB(sb)->ratelimit), fmt , ## args)
#define FAT_PRINTK_PREFIX "%sFAT-fs (%s): "
#define fat_msg(sb, level, fmt, args...) \
do { \
printk_index_subsys_emit(FAT_PRINTK_PREFIX, level, fmt, ##args);\
_fat_msg(sb, level, fmt, ##args); \
} while (0)
__printf(3, 4) __cold
void _fat_msg(struct super_block *sb, const char *level, const char *fmt, ...);
#define fat_msg_ratelimit(sb, level, fmt, args...) \
do { \
if (__ratelimit(&MSDOS_SB(sb)->ratelimit)) \
fat_msg(sb, level, fmt, ## args); \
} while (0)
extern int fat_clusters_flush(struct super_block *sb);
extern int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster);
extern void fat_time_fat2unix(struct msdos_sb_info *sbi, struct timespec64 *ts,
__le16 __time, __le16 __date, u8 time_cs);
extern void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec64 *ts,
__le16 *time, __le16 *date, u8 *time_cs);
extern struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
const struct timespec64 *ts);
#define FAT_UPDATE_ATIME (1u << 0)
#define FAT_UPDATE_CMTIME (1u << 1)
void fat_truncate_time(struct inode *inode, struct timespec64 *now,
unsigned int flags);
int fat_update_time(struct inode *inode, enum fs_update_time type,
unsigned int flags);
extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs);
int fat_cache_init(void);
void fat_cache_destroy(void);
/* fat/nfs.c */
extern const struct export_operations fat_export_ops;
extern const struct export_operations fat_export_ops_nostale;
/* helper for printk */
typedef unsigned long long llu;
#endif /* !_FAT_H */
+395
View File
@@ -0,0 +1,395 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit tests for FAT filesystems.
*
* Copyright (C) 2020 Google LLC.
* Author: David Gow <davidgow@google.com>
*/
#include <kunit/test.h>
#include "fat.h"
static void fat_checksum_test(struct kunit *test)
{
/* With no extension. */
KUNIT_EXPECT_EQ(test, fat_checksum("VMLINUX "), (u8)44);
/* With 3-letter extension. */
KUNIT_EXPECT_EQ(test, fat_checksum("README TXT"), (u8)115);
/* With short (1-letter) extension. */
KUNIT_EXPECT_EQ(test, fat_checksum("ABCDEFGHA "), (u8)98);
}
static void fat_clus_to_blknr_test(struct kunit *test)
{
static const struct msdos_sb_info sbi = {
.sec_per_clus = 4,
.data_start = 100,
};
KUNIT_EXPECT_EQ(test, (sector_t)100,
fat_clus_to_blknr(&sbi, FAT_START_ENT));
KUNIT_EXPECT_EQ(test, (sector_t)112, fat_clus_to_blknr(&sbi, 5));
}
static void fat_get_blknr_offset_test(struct kunit *test)
{
static const struct msdos_sb_info sbi = {
.dir_per_block = 16,
.dir_per_block_bits = 4,
};
sector_t blknr;
int offset;
fat_get_blknr_offset(&sbi, 0, &blknr, &offset);
KUNIT_EXPECT_EQ(test, (sector_t)0, blknr);
KUNIT_EXPECT_EQ(test, 0, offset);
fat_get_blknr_offset(&sbi, (10 << 4) | 7, &blknr, &offset);
KUNIT_EXPECT_EQ(test, (sector_t)10, blknr);
KUNIT_EXPECT_EQ(test, 7, offset);
}
struct fat_timestamp_testcase {
const char *name;
struct timespec64 ts;
__le16 time;
__le16 date;
u8 cs;
int time_offset;
};
struct fat_unix2fat_clamp_testcase {
const char *name;
struct timespec64 ts;
__le16 time;
__le16 date;
u8 cs;
int time_offset;
};
struct fat_truncate_atime_testcase {
const char *name;
struct timespec64 ts;
struct timespec64 expected;
int time_offset;
};
static struct fat_timestamp_testcase time_test_cases[] = {
{
.name = "Earliest possible UTC (1980-01-01 00:00:00)",
.ts = {.tv_sec = 315532800LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(33),
.cs = 0,
.time_offset = 0,
},
{
.name = "Latest possible UTC (2107-12-31 23:59:58)",
.ts = {.tv_sec = 4354819198LL, .tv_nsec = 0L},
.time = cpu_to_le16(49021),
.date = cpu_to_le16(65439),
.cs = 0,
.time_offset = 0,
},
{
.name = "Earliest possible (UTC-11) (== 1979-12-31 13:00:00 UTC)",
.ts = {.tv_sec = 315493200LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(33),
.cs = 0,
.time_offset = 11 * 60,
},
{
.name = "Latest possible (UTC+11) (== 2108-01-01 10:59:58 UTC)",
.ts = {.tv_sec = 4354858798LL, .tv_nsec = 0L},
.time = cpu_to_le16(49021),
.date = cpu_to_le16(65439),
.cs = 0,
.time_offset = -11 * 60,
},
{
.name = "Leap Day / Year (1996-02-29 00:00:00)",
.ts = {.tv_sec = 825552000LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(8285),
.cs = 0,
.time_offset = 0,
},
{
.name = "Year 2000 is leap year (2000-02-29 00:00:00)",
.ts = {.tv_sec = 951782400LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(10333),
.cs = 0,
.time_offset = 0,
},
{
.name = "Year 2100 not leap year (2100-03-01 00:00:00)",
.ts = {.tv_sec = 4107542400LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(61537),
.cs = 0,
.time_offset = 0,
},
{
.name = "Leap year + timezone UTC+1 (== 2004-02-29 00:30:00 UTC)",
.ts = {.tv_sec = 1078014600LL, .tv_nsec = 0L},
.time = cpu_to_le16(48064),
.date = cpu_to_le16(12380),
.cs = 0,
.time_offset = -60,
},
{
.name = "Leap year + timezone UTC-1 (== 2004-02-29 23:30:00 UTC)",
.ts = {.tv_sec = 1078097400LL, .tv_nsec = 0L},
.time = cpu_to_le16(960),
.date = cpu_to_le16(12385),
.cs = 0,
.time_offset = 60,
},
{
.name = "VFAT odd-second resolution (1999-12-31 23:59:59)",
.ts = {.tv_sec = 946684799LL, .tv_nsec = 0L},
.time = cpu_to_le16(49021),
.date = cpu_to_le16(10143),
.cs = 100,
.time_offset = 0,
},
{
.name = "VFAT 10ms resolution (1980-01-01 00:00:00:0010)",
.ts = {.tv_sec = 315532800LL, .tv_nsec = 10000000L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(33),
.cs = 1,
.time_offset = 0,
},
};
static struct fat_unix2fat_clamp_testcase unix2fat_clamp_test_cases[] = {
{
.name = "Clamp to earliest FAT date for 1979-12-31 23:59:59 UTC",
.ts = {.tv_sec = 315532799LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(33),
.cs = 0,
.time_offset = 0,
},
{
.name = "Clamp after time_offset=-60 pushes 1980-01-01 00:30 UTC below 1980",
.ts = {.tv_sec = 315534600LL, .tv_nsec = 0L},
.time = cpu_to_le16(0),
.date = cpu_to_le16(33),
.cs = 0,
.time_offset = -60,
},
{
.name = "Clamp to latest FAT date for 2108-01-01 00:00:00 UTC",
.ts = {.tv_sec = 4354819200LL, .tv_nsec = 0L},
.time = cpu_to_le16(49021),
.date = cpu_to_le16(65439),
.cs = 199,
.time_offset = 0,
},
{
.name = "Clamp after time_offset=60 pushes 2107-12-31 23:30 UTC beyond 2107",
.ts = {.tv_sec = 4354817400LL, .tv_nsec = 0L},
.time = cpu_to_le16(49021),
.date = cpu_to_le16(65439),
.cs = 199,
.time_offset = 60,
},
};
static struct fat_truncate_atime_testcase truncate_atime_test_cases[] = {
{
.name = "UTC atime truncates to 2004-02-29 00:00:00",
.ts = {.tv_sec = 1078058096LL, .tv_nsec = 789000000L},
.expected = {.tv_sec = 1078012800LL, .tv_nsec = 0L},
.time_offset = 0,
},
{
.name = "time_offset=-60 truncates 2004-02-29 00:30 UTC to previous local midnight",
.ts = {.tv_sec = 1078014645LL, .tv_nsec = 123000000L},
.expected = {.tv_sec = 1077930000LL, .tv_nsec = 0L},
.time_offset = -60,
},
{
.name = "time_offset=60 truncates 2004-02-29 23:30 UTC to next local midnight",
.ts = {.tv_sec = 1078097445LL, .tv_nsec = 123000000L},
.expected = {.tv_sec = 1078095600LL, .tv_nsec = 0L},
.time_offset = 60,
},
};
static void time_testcase_desc(struct fat_timestamp_testcase *t,
char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
static void unix2fat_clamp_testcase_desc(struct fat_unix2fat_clamp_testcase *t,
char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
static void truncate_atime_testcase_desc(struct fat_truncate_atime_testcase *t,
char *desc)
{
strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(fat_time, time_test_cases, time_testcase_desc);
KUNIT_ARRAY_PARAM(fat_unix2fat_clamp, unix2fat_clamp_test_cases,
unix2fat_clamp_testcase_desc);
KUNIT_ARRAY_PARAM(fat_truncate_atime, truncate_atime_test_cases,
truncate_atime_testcase_desc);
static void fat_test_set_time_offset(struct msdos_sb_info *sbi, int time_offset)
{
memset(sbi, 0, sizeof(*sbi));
sbi->options.tz_set = 1;
sbi->options.time_offset = time_offset;
}
static void fat_time_fat2unix_test(struct kunit *test)
{
static struct msdos_sb_info fake_sb;
struct timespec64 ts;
struct fat_timestamp_testcase *testcase =
(struct fat_timestamp_testcase *)test->param_value;
fat_test_set_time_offset(&fake_sb, testcase->time_offset);
fat_time_fat2unix(&fake_sb, &ts,
testcase->time,
testcase->date,
testcase->cs);
KUNIT_EXPECT_EQ_MSG(test,
testcase->ts.tv_sec,
ts.tv_sec,
"Timestamp mismatch (seconds)\n");
KUNIT_EXPECT_EQ_MSG(test,
testcase->ts.tv_nsec,
ts.tv_nsec,
"Timestamp mismatch (nanoseconds)\n");
}
static void fat_time_unix2fat_test(struct kunit *test)
{
static struct msdos_sb_info fake_sb;
__le16 date, time;
u8 cs;
struct fat_timestamp_testcase *testcase =
(struct fat_timestamp_testcase *)test->param_value;
fat_test_set_time_offset(&fake_sb, testcase->time_offset);
fat_time_unix2fat(&fake_sb, &testcase->ts,
&time, &date, &cs);
KUNIT_EXPECT_EQ_MSG(test,
testcase->time,
time,
"Time mismatch\n");
KUNIT_EXPECT_EQ_MSG(test,
testcase->date,
date,
"Date mismatch\n");
KUNIT_EXPECT_EQ_MSG(test,
testcase->cs,
cs,
"Centisecond mismatch\n");
}
static void fat_time_unix2fat_clamp_test(struct kunit *test)
{
static struct msdos_sb_info fake_sb;
__le16 date, time;
u8 cs;
struct fat_unix2fat_clamp_testcase *testcase =
(struct fat_unix2fat_clamp_testcase *)test->param_value;
fat_test_set_time_offset(&fake_sb, testcase->time_offset);
fat_time_unix2fat(&fake_sb, &testcase->ts, &time, &date, &cs);
KUNIT_EXPECT_EQ_MSG(test,
testcase->time,
time,
"Clamped time mismatch\n");
KUNIT_EXPECT_EQ_MSG(test,
testcase->date,
date,
"Clamped date mismatch\n");
KUNIT_EXPECT_EQ_MSG(test,
testcase->cs,
cs,
"Clamped centisecond mismatch\n");
}
static void fat_time_unix2fat_no_csec_test(struct kunit *test)
{
static struct msdos_sb_info fake_sb;
struct timespec64 ts = {
.tv_sec = 946684799LL,
.tv_nsec = 0L,
};
__le16 date, time;
fat_test_set_time_offset(&fake_sb, 0);
fat_time_unix2fat(&fake_sb, &ts, &time, &date, NULL);
KUNIT_EXPECT_EQ_MSG(test,
49021,
le16_to_cpu(time),
"Time mismatch without centiseconds\n");
KUNIT_EXPECT_EQ_MSG(test,
10143,
le16_to_cpu(date),
"Date mismatch without centiseconds\n");
}
static void fat_truncate_atime_test(struct kunit *test)
{
static struct msdos_sb_info fake_sb;
struct timespec64 actual;
struct fat_truncate_atime_testcase *testcase =
(struct fat_truncate_atime_testcase *)test->param_value;
fat_test_set_time_offset(&fake_sb, testcase->time_offset);
actual = fat_truncate_atime(&fake_sb, &testcase->ts);
KUNIT_EXPECT_EQ_MSG(test,
testcase->expected.tv_sec,
actual.tv_sec,
"Atime truncation seconds mismatch\n");
KUNIT_EXPECT_EQ_MSG(test,
testcase->expected.tv_nsec,
actual.tv_nsec,
"Atime truncation nanoseconds mismatch\n");
}
static struct kunit_case fat_test_cases[] = {
KUNIT_CASE(fat_checksum_test),
KUNIT_CASE(fat_clus_to_blknr_test),
KUNIT_CASE(fat_get_blknr_offset_test),
KUNIT_CASE_PARAM(fat_time_fat2unix_test, fat_time_gen_params),
KUNIT_CASE_PARAM(fat_time_unix2fat_test, fat_time_gen_params),
KUNIT_CASE_PARAM(fat_time_unix2fat_clamp_test,
fat_unix2fat_clamp_gen_params),
KUNIT_CASE(fat_time_unix2fat_no_csec_test),
KUNIT_CASE_PARAM(fat_truncate_atime_test,
fat_truncate_atime_gen_params),
{},
};
static struct kunit_suite fat_test_suite = {
.name = "fat_test",
.test_cases = fat_test_cases,
};
kunit_test_suites(&fat_test_suite);
MODULE_DESCRIPTION("KUnit tests for FAT filesystems");
MODULE_LICENSE("GPL v2");
+853
View File
@@ -0,0 +1,853 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2004, OGAWA Hirofumi
*/
#include <linux/blkdev.h>
#include <linux/sched/signal.h>
#include <linux/backing-dev-defs.h>
#include "fat.h"
struct fatent_operations {
void (*ent_blocknr)(struct super_block *, int, int *, sector_t *);
void (*ent_set_ptr)(struct fat_entry *, int);
int (*ent_bread)(struct super_block *, struct fat_entry *,
int, sector_t);
int (*ent_get)(struct fat_entry *);
void (*ent_put)(struct fat_entry *, int);
int (*ent_next)(struct fat_entry *);
};
static DEFINE_SPINLOCK(fat12_entry_lock);
static void fat12_ent_blocknr(struct super_block *sb, int entry,
int *offset, sector_t *blocknr)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int bytes = entry + (entry >> 1);
WARN_ON(!fat_valid_entry(sbi, entry));
*offset = bytes & (sb->s_blocksize - 1);
*blocknr = sbi->fat_start + (bytes >> sb->s_blocksize_bits);
}
static void fat_ent_blocknr(struct super_block *sb, int entry,
int *offset, sector_t *blocknr)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int bytes = (entry << sbi->fatent_shift);
WARN_ON(!fat_valid_entry(sbi, entry));
*offset = bytes & (sb->s_blocksize - 1);
*blocknr = sbi->fat_start + (bytes >> sb->s_blocksize_bits);
}
static void fat12_ent_set_ptr(struct fat_entry *fatent, int offset)
{
struct buffer_head **bhs = fatent->bhs;
if (fatent->nr_bhs == 1) {
WARN_ON(offset >= (bhs[0]->b_size - 1));
fatent->u.ent12_p[0] = bhs[0]->b_data + offset;
fatent->u.ent12_p[1] = bhs[0]->b_data + (offset + 1);
} else {
WARN_ON(offset != (bhs[0]->b_size - 1));
fatent->u.ent12_p[0] = bhs[0]->b_data + offset;
fatent->u.ent12_p[1] = bhs[1]->b_data;
}
}
static void fat16_ent_set_ptr(struct fat_entry *fatent, int offset)
{
WARN_ON(offset & (2 - 1));
fatent->u.ent16_p = (__le16 *)(fatent->bhs[0]->b_data + offset);
}
static void fat32_ent_set_ptr(struct fat_entry *fatent, int offset)
{
WARN_ON(offset & (4 - 1));
fatent->u.ent32_p = (__le32 *)(fatent->bhs[0]->b_data + offset);
}
static int fat12_ent_bread(struct super_block *sb, struct fat_entry *fatent,
int offset, sector_t blocknr)
{
struct buffer_head **bhs = fatent->bhs;
WARN_ON(blocknr < MSDOS_SB(sb)->fat_start);
fatent->fat_inode = MSDOS_SB(sb)->fat_inode;
bhs[0] = sb_bread(sb, blocknr);
if (!bhs[0])
goto err;
if ((offset + 1) < sb->s_blocksize)
fatent->nr_bhs = 1;
else {
/* This entry is block boundary, it needs the next block */
blocknr++;
bhs[1] = sb_bread(sb, blocknr);
if (!bhs[1])
goto err_brelse;
fatent->nr_bhs = 2;
}
fat12_ent_set_ptr(fatent, offset);
return 0;
err_brelse:
brelse(bhs[0]);
err:
fat_msg_ratelimit(sb, KERN_ERR, "FAT read failed (blocknr %llu)",
(llu)blocknr);
return -EIO;
}
static int fat_ent_bread(struct super_block *sb, struct fat_entry *fatent,
int offset, sector_t blocknr)
{
const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
WARN_ON(blocknr < MSDOS_SB(sb)->fat_start);
fatent->fat_inode = MSDOS_SB(sb)->fat_inode;
fatent->bhs[0] = sb_bread(sb, blocknr);
if (!fatent->bhs[0]) {
fat_msg_ratelimit(sb, KERN_ERR, "FAT read failed (blocknr %llu)",
(llu)blocknr);
return -EIO;
}
fatent->nr_bhs = 1;
ops->ent_set_ptr(fatent, offset);
return 0;
}
static int fat12_ent_get(struct fat_entry *fatent)
{
u8 **ent12_p = fatent->u.ent12_p;
int next;
spin_lock(&fat12_entry_lock);
if (fatent->entry & 1)
next = (*ent12_p[0] >> 4) | (*ent12_p[1] << 4);
else
next = (*ent12_p[1] << 8) | *ent12_p[0];
spin_unlock(&fat12_entry_lock);
next &= 0x0fff;
if (next >= BAD_FAT12)
next = FAT_ENT_EOF;
return next;
}
static int fat16_ent_get(struct fat_entry *fatent)
{
int next = le16_to_cpu(*fatent->u.ent16_p);
WARN_ON((unsigned long)fatent->u.ent16_p & (2 - 1));
if (next >= BAD_FAT16)
next = FAT_ENT_EOF;
return next;
}
static int fat32_ent_get(struct fat_entry *fatent)
{
int next = le32_to_cpu(*fatent->u.ent32_p) & 0x0fffffff;
WARN_ON((unsigned long)fatent->u.ent32_p & (4 - 1));
if (next >= BAD_FAT32)
next = FAT_ENT_EOF;
return next;
}
static void fat12_ent_put(struct fat_entry *fatent, int new)
{
u8 **ent12_p = fatent->u.ent12_p;
if (new == FAT_ENT_EOF)
new = EOF_FAT12;
spin_lock(&fat12_entry_lock);
if (fatent->entry & 1) {
*ent12_p[0] = (new << 4) | (*ent12_p[0] & 0x0f);
*ent12_p[1] = new >> 4;
} else {
*ent12_p[0] = new & 0xff;
*ent12_p[1] = (*ent12_p[1] & 0xf0) | (new >> 8);
}
spin_unlock(&fat12_entry_lock);
mmb_mark_buffer_dirty(fatent->bhs[0],
&MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
if (fatent->nr_bhs == 2)
mmb_mark_buffer_dirty(fatent->bhs[1],
&MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
}
static void fat16_ent_put(struct fat_entry *fatent, int new)
{
if (new == FAT_ENT_EOF)
new = EOF_FAT16;
*fatent->u.ent16_p = cpu_to_le16(new);
mmb_mark_buffer_dirty(fatent->bhs[0],
&MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
}
static void fat32_ent_put(struct fat_entry *fatent, int new)
{
WARN_ON(new & 0xf0000000);
new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;
*fatent->u.ent32_p = cpu_to_le32(new);
mmb_mark_buffer_dirty(fatent->bhs[0],
&MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
}
static int fat12_ent_next(struct fat_entry *fatent)
{
u8 **ent12_p = fatent->u.ent12_p;
struct buffer_head **bhs = fatent->bhs;
u8 *nextp = ent12_p[1] + 1 + (fatent->entry & 1);
fatent->entry++;
if (fatent->nr_bhs == 1) {
WARN_ON(ent12_p[0] > (u8 *)(bhs[0]->b_data +
(bhs[0]->b_size - 2)));
WARN_ON(ent12_p[1] > (u8 *)(bhs[0]->b_data +
(bhs[0]->b_size - 1)));
if (nextp < (u8 *)(bhs[0]->b_data + (bhs[0]->b_size - 1))) {
ent12_p[0] = nextp - 1;
ent12_p[1] = nextp;
return 1;
}
} else {
WARN_ON(ent12_p[0] != (u8 *)(bhs[0]->b_data +
(bhs[0]->b_size - 1)));
WARN_ON(ent12_p[1] != (u8 *)bhs[1]->b_data);
ent12_p[0] = nextp - 1;
ent12_p[1] = nextp;
brelse(bhs[0]);
bhs[0] = bhs[1];
fatent->nr_bhs = 1;
return 1;
}
ent12_p[0] = NULL;
ent12_p[1] = NULL;
return 0;
}
static int fat16_ent_next(struct fat_entry *fatent)
{
const struct buffer_head *bh = fatent->bhs[0];
fatent->entry++;
if (fatent->u.ent16_p < (__le16 *)(bh->b_data + (bh->b_size - 2))) {
fatent->u.ent16_p++;
return 1;
}
fatent->u.ent16_p = NULL;
return 0;
}
static int fat32_ent_next(struct fat_entry *fatent)
{
const struct buffer_head *bh = fatent->bhs[0];
fatent->entry++;
if (fatent->u.ent32_p < (__le32 *)(bh->b_data + (bh->b_size - 4))) {
fatent->u.ent32_p++;
return 1;
}
fatent->u.ent32_p = NULL;
return 0;
}
static const struct fatent_operations fat12_ops = {
.ent_blocknr = fat12_ent_blocknr,
.ent_set_ptr = fat12_ent_set_ptr,
.ent_bread = fat12_ent_bread,
.ent_get = fat12_ent_get,
.ent_put = fat12_ent_put,
.ent_next = fat12_ent_next,
};
static const struct fatent_operations fat16_ops = {
.ent_blocknr = fat_ent_blocknr,
.ent_set_ptr = fat16_ent_set_ptr,
.ent_bread = fat_ent_bread,
.ent_get = fat16_ent_get,
.ent_put = fat16_ent_put,
.ent_next = fat16_ent_next,
};
static const struct fatent_operations fat32_ops = {
.ent_blocknr = fat_ent_blocknr,
.ent_set_ptr = fat32_ent_set_ptr,
.ent_bread = fat_ent_bread,
.ent_get = fat32_ent_get,
.ent_put = fat32_ent_put,
.ent_next = fat32_ent_next,
};
static inline void lock_fat(struct msdos_sb_info *sbi)
{
mutex_lock(&sbi->fat_lock);
}
static inline void unlock_fat(struct msdos_sb_info *sbi)
{
mutex_unlock(&sbi->fat_lock);
}
void fat_ent_access_init(struct super_block *sb)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
mutex_init(&sbi->fat_lock);
if (is_fat32(sbi)) {
sbi->fatent_shift = 2;
sbi->fatent_ops = &fat32_ops;
} else if (is_fat16(sbi)) {
sbi->fatent_shift = 1;
sbi->fatent_ops = &fat16_ops;
} else if (is_fat12(sbi)) {
sbi->fatent_shift = -1;
sbi->fatent_ops = &fat12_ops;
} else {
fat_fs_error(sb, "invalid FAT variant, %u bits", sbi->fat_bits);
}
}
static void mark_fsinfo_dirty(struct super_block *sb)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
if (sb_rdonly(sb) || !is_fat32(sbi))
return;
__mark_inode_dirty(sbi->fsinfo_inode, I_DIRTY_SYNC);
}
static inline int fat_ent_update_ptr(struct super_block *sb,
struct fat_entry *fatent,
int offset, sector_t blocknr)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct buffer_head **bhs = fatent->bhs;
/* Is this fatent's blocks including this entry? */
if (!fatent->nr_bhs || bhs[0]->b_blocknr != blocknr)
return 0;
if (is_fat12(sbi)) {
if ((offset + 1) < sb->s_blocksize) {
/* This entry is on bhs[0]. */
if (fatent->nr_bhs == 2) {
brelse(bhs[1]);
fatent->nr_bhs = 1;
}
} else {
/* This entry needs the next block. */
if (fatent->nr_bhs != 2)
return 0;
if (bhs[1]->b_blocknr != (blocknr + 1))
return 0;
}
}
ops->ent_set_ptr(fatent, offset);
return 1;
}
int fat_ent_read(struct inode *inode, struct fat_entry *fatent, int entry)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
const struct fatent_operations *ops = sbi->fatent_ops;
int err, offset;
sector_t blocknr;
if (!fat_valid_entry(sbi, entry)) {
fatent_brelse(fatent);
fat_fs_error_ratelimit(sb, "invalid access to FAT (entry 0x%08x)", entry);
return -EIO;
}
fatent_set_entry(fatent, entry);
ops->ent_blocknr(sb, entry, &offset, &blocknr);
if (!fat_ent_update_ptr(sb, fatent, offset, blocknr)) {
fatent_brelse(fatent);
err = ops->ent_bread(sb, fatent, offset, blocknr);
if (err)
return err;
}
return ops->ent_get(fatent);
}
/* FIXME: We can write the blocks as more big chunk. */
static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs,
int nr_bhs)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
struct buffer_head *c_bh;
int err, n, copy;
err = 0;
for (copy = 1; copy < sbi->fats; copy++) {
sector_t backup_fat = sbi->fat_length * copy;
for (n = 0; n < nr_bhs; n++) {
c_bh = sb_getblk(sb, backup_fat + bhs[n]->b_blocknr);
if (!c_bh) {
err = -ENOMEM;
goto error;
}
/* Avoid race with userspace read via bdev */
lock_buffer(c_bh);
memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
set_buffer_uptodate(c_bh);
unlock_buffer(c_bh);
mmb_mark_buffer_dirty(c_bh,
&MSDOS_I(sbi->fat_inode)->i_metadata_bhs);
if (sb->s_flags & SB_SYNCHRONOUS)
err = sync_dirty_buffer(c_bh);
brelse(c_bh);
if (err)
goto error;
}
}
error:
return err;
}
int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
int new, int wait)
{
struct super_block *sb = inode->i_sb;
const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
int err;
ops->ent_put(fatent, new);
if (wait) {
err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs);
if (err)
return err;
}
return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);
}
static inline int fat_ent_next(struct msdos_sb_info *sbi,
struct fat_entry *fatent)
{
if (sbi->fatent_ops->ent_next(fatent)) {
if (fatent->entry < sbi->max_cluster)
return 1;
}
return 0;
}
static inline int fat_ent_read_block(struct super_block *sb,
struct fat_entry *fatent)
{
const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
sector_t blocknr;
int offset;
fatent_brelse(fatent);
ops->ent_blocknr(sb, fatent->entry, &offset, &blocknr);
return ops->ent_bread(sb, fatent, offset, blocknr);
}
static void fat_collect_bhs(struct buffer_head **bhs, int *nr_bhs,
struct fat_entry *fatent)
{
int n, i;
for (n = 0; n < fatent->nr_bhs; n++) {
for (i = 0; i < *nr_bhs; i++) {
if (fatent->bhs[n] == bhs[i])
break;
}
if (i == *nr_bhs) {
get_bh(fatent->bhs[n]);
bhs[i] = fatent->bhs[n];
(*nr_bhs)++;
}
}
}
int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct fat_entry fatent, prev_ent;
struct buffer_head *bhs[MAX_BUF_PER_PAGE];
int i, count, err, nr_bhs, idx_clus;
BUG_ON(nr_cluster > (MAX_BUF_PER_PAGE / 2)); /* fixed limit */
lock_fat(sbi);
if (sbi->free_clusters != -1 && sbi->free_clus_valid &&
sbi->free_clusters < nr_cluster) {
unlock_fat(sbi);
return -ENOSPC;
}
err = nr_bhs = idx_clus = 0;
count = FAT_START_ENT;
fatent_init(&prev_ent);
fatent_init(&fatent);
fatent_set_entry(&fatent, sbi->prev_free + 1);
while (count < sbi->max_cluster) {
if (fatent.entry >= sbi->max_cluster)
fatent.entry = FAT_START_ENT;
fatent_set_entry(&fatent, fatent.entry);
err = fat_ent_read_block(sb, &fatent);
if (err)
goto out;
/* Find the free entries in a block */
do {
if (ops->ent_get(&fatent) == FAT_ENT_FREE) {
int entry = fatent.entry;
/* make the cluster chain */
ops->ent_put(&fatent, FAT_ENT_EOF);
if (prev_ent.nr_bhs)
ops->ent_put(&prev_ent, entry);
fat_collect_bhs(bhs, &nr_bhs, &fatent);
sbi->prev_free = entry;
if (sbi->free_clusters != -1)
sbi->free_clusters--;
cluster[idx_clus] = entry;
idx_clus++;
if (idx_clus == nr_cluster)
goto out;
/*
* fat_collect_bhs() gets ref-count of bhs,
* so we can still use the prev_ent.
*/
prev_ent = fatent;
}
count++;
if (count == sbi->max_cluster)
break;
} while (fat_ent_next(sbi, &fatent));
}
/* Couldn't allocate the free entries */
sbi->free_clusters = 0;
sbi->free_clus_valid = 1;
err = -ENOSPC;
out:
unlock_fat(sbi);
mark_fsinfo_dirty(sb);
fatent_brelse(&fatent);
if (!err) {
if (inode_needs_sync(inode))
err = fat_sync_bhs(bhs, nr_bhs);
if (!err)
err = fat_mirror_bhs(sb, bhs, nr_bhs);
}
for (i = 0; i < nr_bhs; i++)
brelse(bhs[i]);
if (err && idx_clus)
fat_free_clusters(inode, cluster[0]);
return err;
}
int fat_free_clusters(struct inode *inode, int cluster)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct fat_entry fatent;
struct buffer_head *bhs[MAX_BUF_PER_PAGE];
int i, err, nr_bhs;
int first_cl = cluster, dirty_fsinfo = 0;
nr_bhs = 0;
fatent_init(&fatent);
lock_fat(sbi);
do {
cluster = fat_ent_read(inode, &fatent, cluster);
if (cluster < 0) {
err = cluster;
goto error;
} else if (cluster == FAT_ENT_FREE) {
fat_fs_error(sb, "%s: deleting FAT entry beyond EOF",
__func__);
err = -EIO;
goto error;
}
if (sbi->options.discard) {
/*
* Issue discard for the sectors we no longer
* care about, batching contiguous clusters
* into one request
*/
if (cluster != fatent.entry + 1) {
int nr_clus = fatent.entry - first_cl + 1;
sb_issue_discard(sb,
fat_clus_to_blknr(sbi, first_cl),
nr_clus * sbi->sec_per_clus,
GFP_NOFS, 0);
first_cl = cluster;
}
}
ops->ent_put(&fatent, FAT_ENT_FREE);
if (sbi->free_clusters != -1) {
sbi->free_clusters++;
dirty_fsinfo = 1;
}
if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) {
if (sb->s_flags & SB_SYNCHRONOUS) {
err = fat_sync_bhs(bhs, nr_bhs);
if (err)
goto error;
}
err = fat_mirror_bhs(sb, bhs, nr_bhs);
if (err)
goto error;
for (i = 0; i < nr_bhs; i++)
brelse(bhs[i]);
nr_bhs = 0;
}
fat_collect_bhs(bhs, &nr_bhs, &fatent);
} while (cluster != FAT_ENT_EOF);
if (sb->s_flags & SB_SYNCHRONOUS) {
err = fat_sync_bhs(bhs, nr_bhs);
if (err)
goto error;
}
err = fat_mirror_bhs(sb, bhs, nr_bhs);
error:
fatent_brelse(&fatent);
for (i = 0; i < nr_bhs; i++)
brelse(bhs[i]);
unlock_fat(sbi);
if (dirty_fsinfo)
mark_fsinfo_dirty(sb);
return err;
}
EXPORT_SYMBOL_GPL(fat_free_clusters);
struct fatent_ra {
sector_t cur;
sector_t limit;
unsigned int ra_blocks;
sector_t ra_advance;
sector_t ra_next;
sector_t ra_limit;
};
static void fat_ra_init(struct super_block *sb, struct fatent_ra *ra,
struct fat_entry *fatent, int ent_limit)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
sector_t blocknr, block_end;
int offset;
/*
* This is the sequential read, so ra_pages * 2 (but try to
* align the optimal hardware IO size).
* [BTW, 128kb covers the whole sectors for FAT12 and FAT16]
*/
unsigned long ra_pages = sb->s_bdi->ra_pages;
unsigned int reada_blocks;
if (fatent->entry >= ent_limit)
return;
if (ra_pages > sb->s_bdi->io_pages)
ra_pages = rounddown(ra_pages, sb->s_bdi->io_pages);
reada_blocks = ra_pages << (PAGE_SHIFT - sb->s_blocksize_bits + 1);
/* Initialize the range for sequential read */
ops->ent_blocknr(sb, fatent->entry, &offset, &blocknr);
ops->ent_blocknr(sb, ent_limit - 1, &offset, &block_end);
ra->cur = 0;
ra->limit = (block_end + 1) - blocknr;
/* Advancing the window at half size */
ra->ra_blocks = reada_blocks >> 1;
ra->ra_advance = ra->cur;
ra->ra_next = ra->cur;
ra->ra_limit = ra->cur + min_t(sector_t, reada_blocks, ra->limit);
}
/* Assuming to be called before reading a new block (increments ->cur). */
static void fat_ent_reada(struct super_block *sb, struct fatent_ra *ra,
struct fat_entry *fatent)
{
if (ra->ra_next >= ra->ra_limit)
return;
if (ra->cur >= ra->ra_advance) {
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct blk_plug plug;
sector_t blocknr, diff;
int offset;
ops->ent_blocknr(sb, fatent->entry, &offset, &blocknr);
diff = blocknr - ra->cur;
blk_start_plug(&plug);
/*
* FIXME: we would want to directly use the bio with
* pages to reduce the number of segments.
*/
for (; ra->ra_next < ra->ra_limit; ra->ra_next++)
sb_breadahead(sb, ra->ra_next + diff);
blk_finish_plug(&plug);
/* Advance the readahead window */
ra->ra_advance += ra->ra_blocks;
ra->ra_limit += min_t(sector_t,
ra->ra_blocks, ra->limit - ra->ra_limit);
}
ra->cur++;
}
int fat_count_free_clusters(struct super_block *sb)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct fat_entry fatent;
struct fatent_ra fatent_ra;
int err = 0, free;
lock_fat(sbi);
if (sbi->free_clusters != -1 && sbi->free_clus_valid)
goto out;
free = 0;
fatent_init(&fatent);
fatent_set_entry(&fatent, FAT_START_ENT);
fat_ra_init(sb, &fatent_ra, &fatent, sbi->max_cluster);
while (fatent.entry < sbi->max_cluster) {
/* readahead of fat blocks */
fat_ent_reada(sb, &fatent_ra, &fatent);
err = fat_ent_read_block(sb, &fatent);
if (err)
goto out;
do {
if (ops->ent_get(&fatent) == FAT_ENT_FREE)
free++;
} while (fat_ent_next(sbi, &fatent));
cond_resched();
}
sbi->free_clusters = free;
sbi->free_clus_valid = 1;
mark_fsinfo_dirty(sb);
fatent_brelse(&fatent);
out:
unlock_fat(sbi);
return err;
}
static int fat_trim_clusters(struct super_block *sb, u32 clus, u32 nr_clus)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
return sb_issue_discard(sb, fat_clus_to_blknr(sbi, clus),
nr_clus * sbi->sec_per_clus, GFP_NOFS, 0);
}
int fat_trim_fs(struct inode *inode, struct fstrim_range *range)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
const struct fatent_operations *ops = sbi->fatent_ops;
struct fat_entry fatent;
struct fatent_ra fatent_ra;
u64 ent_start, ent_end, minlen, trimmed = 0;
u32 free = 0;
int err = 0;
/*
* FAT data is organized as clusters, trim at the granulary of cluster.
*
* fstrim_range is in byte, convert values to cluster index.
* Treat sectors before data region as all used, not to trim them.
*/
ent_start = max_t(u64, range->start>>sbi->cluster_bits, FAT_START_ENT);
ent_end = ent_start + (range->len >> sbi->cluster_bits) - 1;
minlen = range->minlen >> sbi->cluster_bits;
if (ent_start >= sbi->max_cluster || range->len < sbi->cluster_size)
return -EINVAL;
if (ent_end >= sbi->max_cluster)
ent_end = sbi->max_cluster - 1;
fatent_init(&fatent);
lock_fat(sbi);
fatent_set_entry(&fatent, ent_start);
fat_ra_init(sb, &fatent_ra, &fatent, ent_end + 1);
while (fatent.entry <= ent_end) {
/* readahead of fat blocks */
fat_ent_reada(sb, &fatent_ra, &fatent);
err = fat_ent_read_block(sb, &fatent);
if (err)
goto error;
do {
if (ops->ent_get(&fatent) == FAT_ENT_FREE) {
free++;
} else if (free) {
if (free >= minlen) {
u32 clus = fatent.entry - free;
err = fat_trim_clusters(sb, clus, free);
if (err && err != -EOPNOTSUPP)
goto error;
if (!err)
trimmed += free;
err = 0;
}
free = 0;
}
} while (fat_ent_next(sbi, &fatent) && fatent.entry <= ent_end);
if (fatal_signal_pending(current)) {
err = -ERESTARTSYS;
goto error;
}
if (need_resched()) {
fatent_brelse(&fatent);
unlock_fat(sbi);
cond_resched();
lock_fat(sbi);
}
}
/* handle scenario when tail entries are all free */
if (free && free >= minlen) {
u32 clus = fatent.entry - free;
err = fat_trim_clusters(sb, clus, free);
if (err && err != -EOPNOTSUPP)
goto error;
if (!err)
trimmed += free;
err = 0;
}
error:
fatent_brelse(&fatent);
unlock_fat(sbi);
range->len = trimmed << sbi->cluster_bits;
return err;
}
+615
View File
@@ -0,0 +1,615 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/fat/file.c
*
* Written 1992,1993 by Werner Almesberger
*
* regular file handling primitives for fat-based filesystems
*/
#include <linux/capability.h>
#include <linux/module.h>
#include <linux/compat.h>
#include <linux/mount.h>
#include <linux/blkdev.h>
#include <linux/backing-dev.h>
#include <linux/filelock.h>
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/falloc.h>
#include <linux/fileattr.h>
#include "fat.h"
static long fat_fallocate(struct file *file, int mode,
loff_t offset, loff_t len);
static int fat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr)
{
u32 attr;
inode_lock_shared(inode);
attr = fat_make_attrs(inode);
inode_unlock_shared(inode);
return put_user(attr, user_attr);
}
static int fat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
{
struct inode *inode = file_inode(file);
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
int is_dir = S_ISDIR(inode->i_mode);
u32 attr, oldattr;
struct iattr ia;
int err;
err = get_user(attr, user_attr);
if (err)
goto out;
err = mnt_want_write_file(file);
if (err)
goto out;
inode_lock(inode);
/*
* ATTR_VOLUME and ATTR_DIR cannot be changed; this also
* prevents the user from turning us into a VFAT
* longname entry. Also, we obviously can't set
* any of the NTFS attributes in the high 24 bits.
*/
attr &= 0xff & ~(ATTR_VOLUME | ATTR_DIR);
/* Merge in ATTR_VOLUME and ATTR_DIR */
attr |= (MSDOS_I(inode)->i_attrs & ATTR_VOLUME) |
(is_dir ? ATTR_DIR : 0);
oldattr = fat_make_attrs(inode);
/* Equivalent to a chmod() */
ia.ia_valid = ATTR_MODE | ATTR_CTIME;
ia.ia_ctime = current_time(inode);
if (is_dir)
ia.ia_mode = fat_make_mode(sbi, attr, S_IRWXUGO);
else {
ia.ia_mode = fat_make_mode(sbi, attr,
S_IRUGO | S_IWUGO | (inode->i_mode & S_IXUGO));
}
/* The root directory has no attributes */
if (inode->i_ino == MSDOS_ROOT_INO && attr != ATTR_DIR) {
err = -EINVAL;
goto out_unlock_inode;
}
if (sbi->options.sys_immutable &&
((attr | oldattr) & ATTR_SYS) &&
!capable(CAP_LINUX_IMMUTABLE)) {
err = -EPERM;
goto out_unlock_inode;
}
/*
* The security check is questionable... We single
* out the RO attribute for checking by the security
* module, just because it maps to a file mode.
*/
err = security_inode_setattr(file_mnt_idmap(file),
file->f_path.dentry, &ia);
if (err)
goto out_unlock_inode;
/* This MUST be done before doing anything irreversible... */
err = fat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia);
if (err)
goto out_unlock_inode;
fsnotify_change(file->f_path.dentry, ia.ia_valid);
if (sbi->options.sys_immutable) {
if (attr & ATTR_SYS)
inode->i_flags |= S_IMMUTABLE;
else
inode->i_flags &= ~S_IMMUTABLE;
}
fat_save_attrs(inode, attr);
mark_inode_dirty(inode);
out_unlock_inode:
inode_unlock(inode);
mnt_drop_write_file(file);
out:
return err;
}
static int fat_ioctl_get_volume_id(struct inode *inode, u32 __user *user_attr)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
return put_user(sbi->vol_id, user_attr);
}
static int fat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{
struct super_block *sb = inode->i_sb;
struct fstrim_range __user *user_range;
struct fstrim_range range;
int err;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!bdev_max_discard_sectors(sb->s_bdev))
return -EOPNOTSUPP;
user_range = (struct fstrim_range __user *)arg;
if (copy_from_user(&range, user_range, sizeof(range)))
return -EFAULT;
range.minlen = max(range.minlen, bdev_discard_granularity(sb->s_bdev));
err = fat_trim_fs(inode, &range);
if (err < 0)
return err;
if (copy_to_user(user_range, &range, sizeof(range)))
return -EFAULT;
return 0;
}
long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
u32 __user *user_attr = (u32 __user *)arg;
switch (cmd) {
case FAT_IOCTL_GET_ATTRIBUTES:
return fat_ioctl_get_attributes(inode, user_attr);
case FAT_IOCTL_SET_ATTRIBUTES:
return fat_ioctl_set_attributes(filp, user_attr);
case FAT_IOCTL_GET_VOLUME_ID:
return fat_ioctl_get_volume_id(inode, user_attr);
case FITRIM:
return fat_ioctl_fitrim(inode, arg);
default:
return -ENOTTY; /* Inappropriate ioctl for device */
}
}
static int fat_file_release(struct inode *inode, struct file *filp)
{
if ((filp->f_mode & FMODE_WRITE) &&
MSDOS_SB(inode->i_sb)->options.flush) {
fat_flush_inodes(inode->i_sb, inode, NULL);
set_current_state(TASK_UNINTERRUPTIBLE);
io_schedule_timeout(HZ/10);
}
return 0;
}
int fat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
{
struct inode *inode = filp->f_mapping->host;
struct inode *fat_inode = MSDOS_SB(inode->i_sb)->fat_inode;
int err;
err = mmb_fsync_noflush(filp, &MSDOS_I(inode)->i_metadata_bhs,
start, end, datasync);
if (err)
return err;
err = mmb_sync(&MSDOS_I(fat_inode)->i_metadata_bhs);
if (err)
return err;
return blkdev_issue_flush(inode->i_sb->s_bdev);
}
const struct file_operations fat_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.mmap_prepare = generic_file_mmap_prepare,
.release = fat_file_release,
.unlocked_ioctl = fat_generic_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.fsync = fat_file_fsync,
.splice_read = filemap_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = fat_fallocate,
.setlease = generic_setlease,
};
static int fat_cont_expand(struct inode *inode, loff_t size)
{
struct address_space *mapping = inode->i_mapping;
loff_t start = inode->i_size, count = size - inode->i_size;
int err;
err = generic_cont_expand_simple(inode, size);
if (err)
goto out;
fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
mark_inode_dirty(inode);
if (IS_SYNC(inode)) {
int err2;
/*
* Opencode syncing since we don't have a file open to use
* standard fsync path.
*/
err = filemap_fdatawrite_range(mapping, start,
start + count - 1);
err2 = mmb_sync(&MSDOS_I(inode)->i_metadata_bhs);
if (!err)
err = err2;
err2 = write_inode_now(inode, 1);
if (!err)
err = err2;
if (!err) {
err = filemap_fdatawait_range(mapping, start,
start + count - 1);
}
}
out:
return err;
}
/*
* Preallocate space for a file. This implements fat's fallocate file
* operation, which gets called from sys_fallocate system call. User
* space requests len bytes at offset. If FALLOC_FL_KEEP_SIZE is set
* we just allocate clusters without zeroing them out. Otherwise we
* allocate and zero out clusters via an expanding truncate.
*/
static long fat_fallocate(struct file *file, int mode,
loff_t offset, loff_t len)
{
int nr_cluster; /* Number of clusters to be allocated */
loff_t mm_bytes; /* Number of bytes to be allocated for file */
loff_t ondisksize; /* block aligned on-disk size in bytes*/
struct inode *inode = file->f_mapping->host;
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int err = 0;
/* No support for hole punch or other fallocate flags. */
if (mode & ~FALLOC_FL_KEEP_SIZE)
return -EOPNOTSUPP;
/* No support for dir */
if (!S_ISREG(inode->i_mode))
return -EOPNOTSUPP;
inode_lock(inode);
if (mode & FALLOC_FL_KEEP_SIZE) {
ondisksize = inode->i_blocks << 9;
if ((offset + len) <= ondisksize)
goto error;
/* First compute the number of clusters to be allocated */
mm_bytes = offset + len - ondisksize;
nr_cluster = (mm_bytes + (sbi->cluster_size - 1)) >>
sbi->cluster_bits;
/* Start the allocation.We are not zeroing out the clusters */
while (nr_cluster-- > 0) {
err = fat_add_cluster(inode);
if (err)
goto error;
}
} else {
if ((offset + len) <= i_size_read(inode))
goto error;
/* This is just an expanding truncate */
err = fat_cont_expand(inode, (offset + len));
}
error:
inode_unlock(inode);
return err;
}
/* Free all clusters after the skip'th cluster. */
static int fat_free(struct inode *inode, int skip)
{
struct super_block *sb = inode->i_sb;
int err, wait, free_start, i_start, i_logstart;
if (MSDOS_I(inode)->i_start == 0)
return 0;
fat_cache_inval_inode(inode);
wait = IS_DIRSYNC(inode);
i_start = free_start = MSDOS_I(inode)->i_start;
i_logstart = MSDOS_I(inode)->i_logstart;
/* First, we write the new file size. */
if (!skip) {
MSDOS_I(inode)->i_start = 0;
MSDOS_I(inode)->i_logstart = 0;
}
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
if (wait) {
err = fat_sync_inode(inode);
if (err) {
MSDOS_I(inode)->i_start = i_start;
MSDOS_I(inode)->i_logstart = i_logstart;
return err;
}
} else
mark_inode_dirty(inode);
/* Write a new EOF, and get the remaining cluster chain for freeing. */
if (skip) {
struct fat_entry fatent;
int ret, fclus, dclus;
ret = fat_get_cluster(inode, skip - 1, &fclus, &dclus);
if (ret < 0)
return ret;
else if (ret == FAT_ENT_EOF)
return 0;
fatent_init(&fatent);
ret = fat_ent_read(inode, &fatent, dclus);
if (ret == FAT_ENT_EOF) {
fatent_brelse(&fatent);
return 0;
} else if (ret == FAT_ENT_FREE) {
fat_fs_error(sb,
"%s: invalid cluster chain (i_pos %lld)",
__func__, MSDOS_I(inode)->i_pos);
ret = -EIO;
} else if (ret > 0) {
err = fat_ent_write(inode, &fatent, FAT_ENT_EOF, wait);
if (err)
ret = err;
}
fatent_brelse(&fatent);
if (ret < 0)
return ret;
free_start = ret;
}
inode->i_blocks = skip << (MSDOS_SB(sb)->cluster_bits - 9);
/* Freeing the remained cluster chain */
return fat_free_clusters(inode, free_start);
}
void fat_truncate_blocks(struct inode *inode, loff_t offset)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
const unsigned int cluster_size = sbi->cluster_size;
int nr_clusters;
/*
* This protects against truncating a file bigger than it was then
* trying to write into the hole.
*/
if (MSDOS_I(inode)->mmu_private > offset)
MSDOS_I(inode)->mmu_private = offset;
nr_clusters = (offset + (cluster_size - 1)) >> sbi->cluster_bits;
fat_free(inode, nr_clusters);
fat_flush_inodes(inode->i_sb, inode, NULL);
}
int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
{
struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
bool case_sensitive;
/*
* FAT filesystems are case-insensitive by default. VFAT
* becomes case-sensitive when mounted with 'check=strict',
* which installs vfat_dentry_ops. MSDOS has no such option;
* its 'nocase' mount option selects case-sensitive matching.
*
* VFAT long filename entries preserve case. Without VFAT, only
* uppercased 8.3 short names are stored. MSDOS with 'nocase'
* also preserves case.
*/
if (sbi->options.isvfat)
case_sensitive = sbi->options.name_check == 's';
else
case_sensitive = sbi->options.nocase;
if (!case_sensitive) {
fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
fa->flags |= FS_CASEFOLD_FL;
if (!sbi->options.isvfat)
fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
}
if (d_inode(dentry)->i_flags & S_IMMUTABLE) {
fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
fa->flags |= FS_IMMUTABLE_FL;
}
return 0;
}
EXPORT_SYMBOL_GPL(fat_fileattr_get);
int fat_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, unsigned int flags)
{
struct inode *inode = d_inode(path->dentry);
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
generic_fillattr(idmap, request_mask, inode, stat);
stat->blksize = sbi->cluster_size;
if (sbi->options.nfs == FAT_NFS_NOSTALE_RO) {
/* Use i_pos for ino. This is used as fileid of nfs. */
stat->ino = fat_i_pos_read(sbi, inode);
}
if (sbi->options.isvfat && request_mask & STATX_BTIME) {
stat->result_mask |= STATX_BTIME;
stat->btime = MSDOS_I(inode)->i_crtime;
}
return 0;
}
EXPORT_SYMBOL_GPL(fat_getattr);
static int fat_sanitize_mode(const struct msdos_sb_info *sbi,
struct inode *inode, umode_t *mode_ptr)
{
umode_t mask, perm;
/*
* Note, the basic check is already done by a caller of
* (attr->ia_mode & ~FAT_VALID_MODE)
*/
if (S_ISREG(inode->i_mode))
mask = sbi->options.fs_fmask;
else
mask = sbi->options.fs_dmask;
perm = *mode_ptr & ~(S_IFMT | mask);
/*
* Of the r and x bits, all (subject to umask) must be present. Of the
* w bits, either all (subject to umask) or none must be present.
*
* If fat_mode_can_hold_ro(inode) is false, can't change w bits.
*/
if ((perm & (S_IRUGO | S_IXUGO)) != (inode->i_mode & (S_IRUGO|S_IXUGO)))
return -EPERM;
if (fat_mode_can_hold_ro(inode)) {
if ((perm & S_IWUGO) && ((perm & S_IWUGO) != (S_IWUGO & ~mask)))
return -EPERM;
} else {
if ((perm & S_IWUGO) != (S_IWUGO & ~mask))
return -EPERM;
}
*mode_ptr &= S_IFMT | perm;
return 0;
}
static int fat_allow_set_time(struct mnt_idmap *idmap,
struct msdos_sb_info *sbi, struct inode *inode)
{
umode_t allow_utime = sbi->options.allow_utime;
if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode),
current_fsuid())) {
if (vfsgid_in_group_p(i_gid_into_vfsgid(idmap, inode)))
allow_utime >>= 3;
if (allow_utime & MAY_WRITE)
return 1;
}
/* use a default check */
return 0;
}
#define TIMES_SET_FLAGS (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)
/* valid file mode bits */
#define FAT_VALID_MODE (S_IFREG | S_IFDIR | S_IRWXUGO)
int fat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr)
{
struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
struct inode *inode = d_inode(dentry);
unsigned int ia_valid;
int error;
/* Check for setting the inode time. */
ia_valid = attr->ia_valid;
if (ia_valid & TIMES_SET_FLAGS) {
if (fat_allow_set_time(idmap, sbi, inode))
attr->ia_valid &= ~TIMES_SET_FLAGS;
}
error = setattr_prepare(idmap, dentry, attr);
attr->ia_valid = ia_valid;
if (error) {
if (sbi->options.quiet)
error = 0;
goto out;
}
/*
* Expand the file. Since inode_setattr() updates ->i_size
* before calling the ->truncate(), but FAT needs to fill the
* hole before it. XXX: this is no longer true with new truncate
* sequence.
*/
if (attr->ia_valid & ATTR_SIZE) {
inode_dio_wait(inode);
if (attr->ia_size > inode->i_size) {
error = fat_cont_expand(inode, attr->ia_size);
if (error || attr->ia_valid == ATTR_SIZE)
goto out;
attr->ia_valid &= ~ATTR_SIZE;
}
}
if (((attr->ia_valid & ATTR_UID) &&
(!uid_eq(from_vfsuid(idmap, i_user_ns(inode), attr->ia_vfsuid),
sbi->options.fs_uid))) ||
((attr->ia_valid & ATTR_GID) &&
(!gid_eq(from_vfsgid(idmap, i_user_ns(inode), attr->ia_vfsgid),
sbi->options.fs_gid))) ||
((attr->ia_valid & ATTR_MODE) &&
(attr->ia_mode & ~FAT_VALID_MODE)))
error = -EPERM;
if (error) {
if (sbi->options.quiet)
error = 0;
goto out;
}
/*
* We don't return -EPERM here. Yes, strange, but this is too
* old behavior.
*/
if (attr->ia_valid & ATTR_MODE) {
if (fat_sanitize_mode(sbi, inode, &attr->ia_mode) < 0)
attr->ia_valid &= ~ATTR_MODE;
}
if (attr->ia_valid & ATTR_SIZE) {
error = fat_block_truncate_page(inode, attr->ia_size);
if (error)
goto out;
down_write(&MSDOS_I(inode)->truncate_lock);
truncate_setsize(inode, attr->ia_size);
fat_truncate_blocks(inode, attr->ia_size);
up_write(&MSDOS_I(inode)->truncate_lock);
}
/*
* setattr_copy can't truncate these appropriately, so we'll copy them
* ourselves. See fat_truncate_time for the c/mtime logic on fat.
*/
if (attr->ia_valid & ATTR_ATIME)
fat_truncate_time(inode, &attr->ia_atime, FAT_UPDATE_ATIME);
if (attr->ia_valid & ATTR_MTIME)
fat_truncate_time(inode, &attr->ia_mtime, FAT_UPDATE_CMTIME);
attr->ia_valid &= ~(ATTR_ATIME|ATTR_CTIME|ATTR_MTIME);
setattr_copy(idmap, inode, attr);
mark_inode_dirty(inode);
out:
return error;
}
EXPORT_SYMBOL_GPL(fat_setattr);
const struct inode_operations fat_file_inode_operations = {
.setattr = fat_setattr,
.getattr = fat_getattr,
.fileattr_get = fat_fileattr_get,
.update_time = fat_update_time,
};
File diff suppressed because it is too large Load Diff
+362
View File
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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)
*/
#include "fat.h"
#include <linux/iversion.h>
/*
* 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.
*/
void __fat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
{
struct fat_mount_options *opts = &MSDOS_SB(sb)->options;
va_list args;
struct va_format vaf;
if (report) {
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
fat_msg(sb, KERN_ERR, "error, %pV", &vaf);
va_end(args);
}
if (opts->errors == FAT_ERRORS_PANIC)
panic("FAT-fs (%s): fs panic from previous error\n", sb->s_id);
else if (opts->errors == FAT_ERRORS_RO && !sb_rdonly(sb)) {
sb->s_flags |= SB_RDONLY;
fat_msg(sb, KERN_ERR, "Filesystem has been set read-only");
}
}
EXPORT_SYMBOL_GPL(__fat_fs_error);
/**
* _fat_msg() - Print a preformatted FAT message based on a superblock.
* @sb: A pointer to a &struct super_block
* @level: A Kernel printk level constant
* @fmt: The printf-style format string to print.
*
* Everything that is not fat_fs_error() should be fat_msg().
*
* fat_msg() wraps _fat_msg() for printk indexing.
*/
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(FAT_PRINTK_PREFIX "%pV\n", level, sb->s_id, &vaf);
va_end(args);
}
/* Flushes the number of free clusters on FAT32 */
/* XXX: Need to write one per FSINFO block. Currently only writes 1 */
int fat_clusters_flush(struct super_block *sb)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
struct buffer_head *bh;
struct fat_boot_fsinfo *fsinfo;
if (!is_fat32(sbi))
return 0;
bh = sb_bread(sb, sbi->fsinfo_sector);
if (bh == NULL) {
fat_msg(sb, KERN_ERR, "bread failed in fat_clusters_flush");
return -EIO;
}
fsinfo = (struct fat_boot_fsinfo *)bh->b_data;
/* Sanity check */
if (!IS_FSINFO(fsinfo)) {
fat_msg(sb, KERN_ERR, "Invalid FSINFO signature: "
"0x%08x, 0x%08x (sector = %lu)",
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);
return 0;
}
/*
* 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;
/*
* FIXME:Although we can add this cache, fat_cache_add() is
* assuming to be called after linear search with fat_cache_id.
*/
// fat_cache_add(inode, new_fclus, new_dclus);
} else {
MSDOS_I(inode)->i_start = new_dclus;
MSDOS_I(inode)->i_logstart = new_dclus;
/*
* Since generic_write_sync() synchronizes regular files later,
* we sync here only directories.
*/
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))) {
fat_fs_error_ratelimit(
sb, "clusters badly computed (%d != %llu)", new_fclus,
(llu)(inode->i_blocks >> (sbi->cluster_bits - 9)));
fat_cache_inval_inode(inode);
}
inode->i_blocks += nr_cluster << (sbi->cluster_bits - 9);
return 0;
}
/*
* 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) 2sec 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)
/* Linear day numbers of the respective 1sts in non-leap years. */
static long days_in_year[] = {
/* 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,
};
static inline int fat_tz_offset(const struct msdos_sb_info *sbi)
{
return (sbi->options.tz_set ?
-sbi->options.time_offset :
sys_tz.tz_minuteswest) * SECS_PER_MIN;
}
/* Convert a FAT time/date pair to a UNIX date (seconds since 1 1 70). */
void fat_time_fat2unix(struct msdos_sb_info *sbi, struct timespec64 *ts,
__le16 __time, __le16 __date, u8 time_cs)
{
u16 time = le16_to_cpu(__time), date = le16_to_cpu(__date);
time64_t second;
long day, leap_day, month, year;
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;
second += (time64_t)(year * 365 + leap_day
+ days_in_year[month] + day
+ DAYS_DELTA) * SECS_PER_DAY;
second += fat_tz_offset(sbi);
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;
}
}
/* Export fat_time_fat2unix() for the fat_test KUnit tests. */
EXPORT_SYMBOL_GPL(fat_time_fat2unix);
/* Convert linear UNIX date to a FAT time/date pair. */
void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec64 *ts,
__le16 *time, __le16 *date, u8 *time_cs)
{
struct tm tm;
time64_to_tm(ts->tv_sec, -fat_tz_offset(sbi), &tm);
/* FAT can only support year between 1980 to 2107 */
if (tm.tm_year < 1980 - 1900) {
*time = 0;
*date = cpu_to_le16((0 << 9) | (1 << 5) | 1);
if (time_cs)
*time_cs = 0;
return;
}
if (tm.tm_year > 2107 - 1900) {
*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;
}
/* 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;
*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);
if (time_cs)
*time_cs = (ts->tv_sec & 1) * 100 + ts->tv_nsec / 10000000;
}
EXPORT_SYMBOL_GPL(fat_time_unix2fat);
static inline struct timespec64 fat_timespec64_trunc_2secs(struct timespec64 ts)
{
return (struct timespec64){ ts.tv_sec & ~1ULL, 0 };
}
/*
* truncate atime to 24 hour granularity (00:00:00 in local timezone)
*/
struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
const struct timespec64 *ts)
{
/* to localtime */
time64_t seconds = ts->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;
return (struct timespec64){ seconds, 0 };
}
/* Export fat_truncate_atime() for the fat_test KUnit tests. */
EXPORT_SYMBOL_GPL(fat_truncate_atime);
/*
* Update the in-inode atime and/or mtime after truncating the timestamp to the
* granularity. All timestamps in root inode are always 0.
*
* ctime and mtime share the same on-disk field, and should be identical in
* memory. All mtime updates will be applied to ctime, but ctime updates are
* ignored.
*/
void fat_truncate_time(struct inode *inode, struct timespec64 *now,
unsigned int flags)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
struct timespec64 ts;
if (inode->i_ino == MSDOS_ROOT_INO)
return;
if (now == NULL) {
now = &ts;
ts = current_time(inode);
}
if (flags & FAT_UPDATE_ATIME)
inode_set_atime_to_ts(inode, fat_truncate_atime(sbi, now));
if (flags & FAT_UPDATE_CMTIME) {
/* truncate mtime to 2 second granularity */
struct timespec64 mtime = fat_timespec64_trunc_2secs(*now);
inode_set_mtime_to_ts(inode, mtime);
inode_set_ctime_to_ts(inode, mtime);
}
}
EXPORT_SYMBOL_GPL(fat_truncate_time);
int fat_update_time(struct inode *inode, enum fs_update_time type,
unsigned int flags)
{
if (inode->i_ino != MSDOS_ROOT_INO) {
fat_truncate_time(inode, NULL, type == FS_UPD_ATIME ?
FAT_UPDATE_ATIME : FAT_UPDATE_CMTIME);
__mark_inode_dirty(inode, inode_time_dirty_flag(inode));
}
return 0;
}
EXPORT_SYMBOL_GPL(fat_update_time);
int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs)
{
int i, err = 0;
for (i = 0; i < nr_bhs; i++)
write_dirty_buffer(bhs[i], 0);
for (i = 0; i < nr_bhs; i++) {
wait_on_buffer(bhs[i]);
if (!err && !buffer_uptodate(bhs[i]))
err = -EIO;
}
return err;
}
+718
View File
@@ -0,0 +1,718 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/fs/msdos/namei.c
*
* Written 1992,1993 by Werner Almesberger
* Hidden files 1995 by Albert Cahalan <albert@ccs.neu.edu> <adc@coe.neu.edu>
* Rewritten for constant inumbers 1999 by Al Viro
*/
#include <linux/module.h>
#include <linux/iversion.h>
#include "fat.h"
/* Characters that are undesirable in an MS-DOS file name */
static unsigned char bad_chars[] = "*?<>|\"";
static unsigned char bad_if_strict[] = "+=,; ";
/***** Formats an MS-DOS file name. Rejects invalid names. */
static int msdos_format_name(const unsigned char *name, int len,
unsigned char *res, struct fat_mount_options *opts)
/*
* name is the proposed name, len is its length, res is
* the resulting name, opts->name_check is either (r)elaxed,
* (n)ormal or (s)trict, opts->dotsOK allows dots at the
* beginning of name (for hidden files)
*/
{
unsigned char *walk;
unsigned char c;
int space;
if (name[0] == '.') { /* dotfile because . and .. already done */
if (opts->dotsOK) {
/* Get rid of dot - test for it elsewhere */
name++;
len--;
} else
return -EINVAL;
}
/*
* disallow names that _really_ start with a dot
*/
space = 1;
c = 0;
for (walk = res; len && walk - res < 8; walk++) {
c = *name++;
len--;
if (opts->name_check != 'r' && strchr(bad_chars, c))
return -EINVAL;
if (opts->name_check == 's' && strchr(bad_if_strict, c))
return -EINVAL;
if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
return -EINVAL;
if (c < ' ' || c == ':' || c == '\\')
return -EINVAL;
/*
* 0xE5 is legal as a first character, but we must substitute
* 0x05 because 0xE5 marks deleted files. Yes, DOS really
* does this.
* It seems that Microsoft hacked DOS to support non-US
* characters after the 0xE5 character was already in use to
* mark deleted files.
*/
if ((res == walk) && (c == 0xE5))
c = 0x05;
if (c == '.')
break;
space = (c == ' ');
*walk = (!opts->nocase && c >= 'a' && c <= 'z') ? c - 32 : c;
}
if (space)
return -EINVAL;
if (opts->name_check == 's' && len && c != '.') {
c = *name++;
len--;
if (c != '.')
return -EINVAL;
}
while (c != '.' && len--)
c = *name++;
if (c == '.') {
while (walk - res < 8)
*walk++ = ' ';
while (len > 0 && walk - res < MSDOS_NAME) {
c = *name++;
len--;
if (opts->name_check != 'r' && strchr(bad_chars, c))
return -EINVAL;
if (opts->name_check == 's' &&
strchr(bad_if_strict, c))
return -EINVAL;
if (c < ' ' || c == ':' || c == '\\')
return -EINVAL;
if (c == '.') {
if (opts->name_check == 's')
return -EINVAL;
break;
}
if (c >= 'A' && c <= 'Z' && opts->name_check == 's')
return -EINVAL;
space = c == ' ';
if (!opts->nocase && c >= 'a' && c <= 'z')
*walk++ = c - 32;
else
*walk++ = c;
}
if (space)
return -EINVAL;
if (opts->name_check == 's' && len)
return -EINVAL;
}
while (walk - res < MSDOS_NAME)
*walk++ = ' ';
return 0;
}
/***** Locates a directory entry. Uses unformatted name. */
static int msdos_find(struct inode *dir, const unsigned char *name, int len,
struct fat_slot_info *sinfo)
{
struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
unsigned char msdos_name[MSDOS_NAME];
int err;
err = msdos_format_name(name, len, msdos_name, &sbi->options);
if (err)
return -ENOENT;
err = fat_scan(dir, msdos_name, sinfo);
if (!err && sbi->options.dotsOK) {
if (name[0] == '.') {
if (!(sinfo->de->attr & ATTR_HIDDEN))
err = -ENOENT;
} else {
if (sinfo->de->attr & ATTR_HIDDEN)
err = -ENOENT;
}
if (err)
brelse(sinfo->bh);
}
return err;
}
/*
* Compute the hash for the msdos name corresponding to the dentry.
* Note: if the name is invalid, we leave the hash code unchanged so
* that the existing dentry can be used. The msdos fs routines will
* return ENOENT or EINVAL as appropriate.
*/
static int msdos_hash(const struct dentry *dentry, struct qstr *qstr)
{
struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
unsigned char msdos_name[MSDOS_NAME];
int error;
error = msdos_format_name(qstr->name, qstr->len, msdos_name, options);
if (!error)
qstr->hash = full_name_hash(dentry, msdos_name, MSDOS_NAME);
return 0;
}
/*
* Compare two msdos names. If either of the names are invalid,
* we fall back to doing the standard name comparison.
*/
static int msdos_cmp(const struct dentry *dentry,
unsigned int len, const char *str, const struct qstr *name)
{
struct fat_mount_options *options = &MSDOS_SB(dentry->d_sb)->options;
unsigned char a_msdos_name[MSDOS_NAME], b_msdos_name[MSDOS_NAME];
int error;
error = msdos_format_name(name->name, name->len, a_msdos_name, options);
if (error)
goto old_compare;
error = msdos_format_name(str, len, b_msdos_name, options);
if (error)
goto old_compare;
error = memcmp(a_msdos_name, b_msdos_name, MSDOS_NAME);
out:
return error;
old_compare:
error = 1;
if (name->len == len)
error = memcmp(name->name, str, len);
goto out;
}
static const struct dentry_operations msdos_dentry_operations = {
.d_hash = msdos_hash,
.d_compare = msdos_cmp,
};
/*
* AV. Wrappers for FAT sb operations. Is it wise?
*/
/***** Get inode using directory and name */
static struct dentry *msdos_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct super_block *sb = dir->i_sb;
struct fat_slot_info sinfo;
struct inode *inode;
int err;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
switch (err) {
case -ENOENT:
inode = NULL;
break;
case 0:
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(sinfo.bh);
break;
default:
inode = ERR_PTR(err);
}
mutex_unlock(&MSDOS_SB(sb)->s_lock);
return d_splice_alias(inode, dentry);
}
/***** Creates a directory entry (name is already formatted). */
static int msdos_add_entry(struct inode *dir, const unsigned char *name,
int is_dir, int is_hid, int cluster,
struct timespec64 *ts, struct fat_slot_info *sinfo)
{
struct msdos_sb_info *sbi = MSDOS_SB(dir->i_sb);
struct msdos_dir_entry de;
__le16 time, date;
int err;
memcpy(de.name, name, MSDOS_NAME);
de.attr = is_dir ? ATTR_DIR : ATTR_ARCH;
if (is_hid)
de.attr |= ATTR_HIDDEN;
de.lcase = 0;
fat_time_unix2fat(sbi, ts, &time, &date, NULL);
de.cdate = de.adate = 0;
de.ctime = 0;
de.ctime_cs = 0;
de.time = time;
de.date = date;
fat_set_start(&de, cluster);
de.size = 0;
err = fat_add_entries(dir, &de, 1, sinfo);
if (err)
return err;
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(dir))
(void)fat_sync_inode(dir);
else
mark_inode_dirty(dir);
return 0;
}
/***** Create a file */
static int msdos_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = NULL;
struct fat_slot_info sinfo;
struct timespec64 ts;
unsigned char msdos_name[MSDOS_NAME];
int err, is_hid;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
msdos_name, &MSDOS_SB(sb)->options);
if (err)
goto out;
is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
/* Have to do it due to foo vs. .foo conflicts */
if (!fat_scan(dir, msdos_name, &sinfo)) {
brelse(sinfo.bh);
err = -EINVAL;
goto out;
}
ts = current_time(dir);
err = msdos_add_entry(dir, msdos_name, 0, is_hid, 0, &ts, &sinfo);
if (err)
goto out;
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(sinfo.bh);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto out;
}
fat_truncate_time(inode, &ts, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, dir, inode);
return err;
}
/***** Remove a directory */
static int msdos_rmdir(struct inode *dir, struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = d_inode(dentry);
struct fat_slot_info sinfo;
int err;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = fat_dir_empty(inode);
if (err)
goto out;
err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
if (err)
goto out;
err = fat_remove_entries(dir, &sinfo); /* and releases bh */
if (err)
goto out;
if (dir->i_nlink >= 3)
drop_nlink(dir);
else {
fat_fs_error(sb, "parent dir link count too low (%u)",
dir->i_nlink);
}
clear_nlink(inode);
fat_detach(inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, dir, inode);
return err;
}
/***** Make a directory */
static struct dentry *msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
struct super_block *sb = dir->i_sb;
struct fat_slot_info sinfo;
struct inode *inode;
unsigned char msdos_name[MSDOS_NAME];
struct timespec64 ts;
int err, is_hid, cluster;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_format_name(dentry->d_name.name, dentry->d_name.len,
msdos_name, &MSDOS_SB(sb)->options);
if (err)
goto out;
is_hid = (dentry->d_name.name[0] == '.') && (msdos_name[0] != '.');
/* foo vs .foo situation */
if (!fat_scan(dir, msdos_name, &sinfo)) {
brelse(sinfo.bh);
err = -EINVAL;
goto out;
}
ts = current_time(dir);
cluster = fat_alloc_new_dir(dir, &ts);
if (cluster < 0) {
err = cluster;
goto out;
}
err = msdos_add_entry(dir, msdos_name, 1, is_hid, cluster, &ts, &sinfo);
if (err)
goto out_free;
inc_nlink(dir);
inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(sinfo.bh);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
/* the directory was completed, just return a error */
goto out;
}
set_nlink(inode, 2);
fat_truncate_time(inode, &ts, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode);
mutex_unlock(&MSDOS_SB(sb)->s_lock);
fat_flush_inodes(sb, dir, inode);
return NULL;
out_free:
fat_free_clusters(dir, cluster);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
return ERR_PTR(err);
}
/***** Unlink a file */
static int msdos_unlink(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct fat_slot_info sinfo;
int err;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_find(dir, dentry->d_name.name, dentry->d_name.len, &sinfo);
if (err)
goto out;
err = fat_remove_entries(dir, &sinfo); /* and releases bh */
if (err)
goto out;
clear_nlink(inode);
fat_detach(inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, dir, inode);
return err;
}
static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
struct dentry *old_dentry,
struct inode *new_dir, unsigned char *new_name,
struct dentry *new_dentry, int is_hid)
{
struct buffer_head *dotdot_bh;
struct msdos_dir_entry *dotdot_de;
struct inode *old_inode, *new_inode;
struct fat_slot_info old_sinfo, sinfo;
struct timespec64 ts;
loff_t new_i_pos;
int err, old_attrs, is_dir, update_dotdot, corrupt = 0;
old_sinfo.bh = sinfo.bh = dotdot_bh = NULL;
old_inode = d_inode(old_dentry);
new_inode = d_inode(new_dentry);
err = fat_scan(old_dir, old_name, &old_sinfo);
if (err) {
err = -EIO;
goto out;
}
is_dir = S_ISDIR(old_inode->i_mode);
update_dotdot = (is_dir && old_dir != new_dir);
if (update_dotdot) {
if (fat_get_dotdot_entry(old_inode, &dotdot_bh, &dotdot_de)) {
err = -EIO;
goto out;
}
}
old_attrs = MSDOS_I(old_inode)->i_attrs;
err = fat_scan(new_dir, new_name, &sinfo);
if (!err) {
if (!new_inode) {
/* "foo" -> ".foo" case. just change the ATTR_HIDDEN */
if (sinfo.de != old_sinfo.de) {
err = -EINVAL;
goto out;
}
if (is_hid)
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
if (IS_DIRSYNC(old_dir)) {
err = fat_sync_inode(old_inode);
if (err) {
MSDOS_I(old_inode)->i_attrs = old_attrs;
goto out;
}
} else
mark_inode_dirty(old_inode);
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, NULL, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(old_dir))
(void)fat_sync_inode(old_dir);
else
mark_inode_dirty(old_dir);
goto out;
}
}
ts = current_time(old_inode);
if (new_inode) {
if (err)
goto out;
if (is_dir) {
err = fat_dir_empty(new_inode);
if (err)
goto out;
}
new_i_pos = MSDOS_I(new_inode)->i_pos;
fat_detach(new_inode);
} else {
err = msdos_add_entry(new_dir, new_name, is_dir, is_hid, 0,
&ts, &sinfo);
if (err)
goto out;
new_i_pos = sinfo.i_pos;
}
inode_inc_iversion(new_dir);
fat_detach(old_inode);
fat_attach(old_inode, new_i_pos);
if (is_hid)
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
if (IS_DIRSYNC(new_dir)) {
err = fat_sync_inode(old_inode);
if (err)
goto error_inode;
} else
mark_inode_dirty(old_inode);
if (update_dotdot) {
fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
mmb_mark_buffer_dirty(dotdot_bh,
&MSDOS_I(old_inode)->i_metadata_bhs);
if (IS_DIRSYNC(new_dir)) {
err = sync_dirty_buffer(dotdot_bh);
if (err)
goto error_dotdot;
}
drop_nlink(old_dir);
if (!new_inode)
inc_nlink(new_dir);
}
err = fat_remove_entries(old_dir, &old_sinfo); /* and releases bh */
old_sinfo.bh = NULL;
if (err)
goto error_dotdot;
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, &ts, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(old_dir))
(void)fat_sync_inode(old_dir);
else
mark_inode_dirty(old_dir);
if (new_inode) {
drop_nlink(new_inode);
if (is_dir)
drop_nlink(new_inode);
}
out:
brelse(sinfo.bh);
brelse(dotdot_bh);
brelse(old_sinfo.bh);
return err;
error_dotdot:
/* data cluster is shared, serious corruption */
corrupt = 1;
if (update_dotdot) {
fat_set_start(dotdot_de, MSDOS_I(old_dir)->i_logstart);
mmb_mark_buffer_dirty(dotdot_bh,
&MSDOS_I(old_inode)->i_metadata_bhs);
corrupt |= sync_dirty_buffer(dotdot_bh);
}
error_inode:
fat_detach(old_inode);
fat_attach(old_inode, old_sinfo.i_pos);
MSDOS_I(old_inode)->i_attrs = old_attrs;
if (new_inode) {
fat_attach(new_inode, new_i_pos);
if (corrupt)
corrupt |= fat_sync_inode(new_inode);
} else {
/*
* If new entry was not sharing the data cluster, it
* shouldn't be serious corruption.
*/
int err2 = fat_remove_entries(new_dir, &sinfo);
if (corrupt)
corrupt |= err2;
sinfo.bh = NULL;
}
if (corrupt < 0) {
fat_fs_error(new_dir->i_sb,
"%s: Filesystem corrupted (i_pos %lld)",
__func__, sinfo.i_pos);
}
goto out;
}
/***** Rename, a wrapper for rename_same_dir & rename_diff_dir */
static int msdos_rename(struct mnt_idmap *idmap,
struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
{
struct super_block *sb = old_dir->i_sb;
unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
int err, is_hid;
if (flags & ~RENAME_NOREPLACE)
return -EINVAL;
mutex_lock(&MSDOS_SB(sb)->s_lock);
err = msdos_format_name(old_dentry->d_name.name,
old_dentry->d_name.len, old_msdos_name,
&MSDOS_SB(old_dir->i_sb)->options);
if (err)
goto out;
err = msdos_format_name(new_dentry->d_name.name,
new_dentry->d_name.len, new_msdos_name,
&MSDOS_SB(new_dir->i_sb)->options);
if (err)
goto out;
is_hid =
(new_dentry->d_name.name[0] == '.') && (new_msdos_name[0] != '.');
err = do_msdos_rename(old_dir, old_msdos_name, old_dentry,
new_dir, new_msdos_name, new_dentry, is_hid);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
if (!err)
err = fat_flush_inodes(sb, old_dir, new_dir);
return err;
}
static const struct inode_operations msdos_dir_inode_operations = {
.create = msdos_create,
.lookup = msdos_lookup,
.unlink = msdos_unlink,
.mkdir = msdos_mkdir,
.rmdir = msdos_rmdir,
.rename = msdos_rename,
.setattr = fat_setattr,
.getattr = fat_getattr,
.fileattr_get = fat_fileattr_get,
.update_time = fat_update_time,
};
static void setup(struct super_block *sb)
{
MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;
set_default_d_op(sb, &msdos_dentry_operations);
sb->s_flags |= SB_NOATIME;
}
static int msdos_fill_super(struct super_block *sb, struct fs_context *fc)
{
return fat_fill_super(sb, fc, setup);
}
static int msdos_get_tree(struct fs_context *fc)
{
return get_tree_bdev(fc, msdos_fill_super);
}
static int msdos_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
return fat_parse_param(fc, param, false);
}
static const struct fs_context_operations msdos_context_ops = {
.parse_param = msdos_parse_param,
.get_tree = msdos_get_tree,
.reconfigure = fat_reconfigure,
.free = fat_free_fc,
};
static int msdos_init_fs_context(struct fs_context *fc)
{
int err;
/* Initialize with is_vfat == false */
err = fat_init_fs_context(fc, false);
if (err)
return err;
fc->ops = &msdos_context_ops;
return 0;
}
static struct file_system_type msdos_fs_type = {
.owner = THIS_MODULE,
.name = "msdos",
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
.init_fs_context = msdos_init_fs_context,
.parameters = fat_param_spec,
};
MODULE_ALIAS_FS("msdos");
static int __init init_msdos_fs(void)
{
return register_filesystem(&msdos_fs_type);
}
static void __exit exit_msdos_fs(void)
{
unregister_filesystem(&msdos_fs_type);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Werner Almesberger");
MODULE_DESCRIPTION("MS-DOS filesystem support");
module_init(init_msdos_fs)
module_exit(exit_msdos_fs)
File diff suppressed because it is too large Load Diff
+299
View File
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0-only
/* fs/fat/nfs.c
*/
#include <linux/exportfs.h>
#include "fat.h"
struct fat_fid {
u32 i_gen;
u32 i_pos_low;
u16 i_pos_hi;
u16 parent_i_pos_hi;
u32 parent_i_pos_low;
u32 parent_i_gen;
};
#define FAT_FID_SIZE_WITHOUT_PARENT 3
#define FAT_FID_SIZE_WITH_PARENT (sizeof(struct fat_fid)/sizeof(u32))
/*
* Look up a directory inode given its starting cluster.
*/
static struct inode *fat_dget(struct super_block *sb, int i_logstart)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
struct hlist_head *head;
struct msdos_inode_info *i;
struct inode *inode = NULL;
head = sbi->dir_hashtable + fat_dir_hash(i_logstart);
spin_lock(&sbi->dir_hash_lock);
hlist_for_each_entry(i, head, i_dir_hash) {
BUG_ON(i->vfs_inode.i_sb != sb);
if (i->i_logstart != i_logstart)
continue;
inode = igrab(&i->vfs_inode);
if (inode)
break;
}
spin_unlock(&sbi->dir_hash_lock);
return inode;
}
static struct inode *fat_ilookup(struct super_block *sb, u64 ino, loff_t i_pos)
{
if (MSDOS_SB(sb)->options.nfs == FAT_NFS_NOSTALE_RO)
return fat_iget(sb, i_pos);
else {
if ((ino < MSDOS_ROOT_INO) || (ino == MSDOS_FSINFO_INO))
return NULL;
return ilookup(sb, ino);
}
}
static struct inode *__fat_nfs_get_inode(struct super_block *sb,
u64 ino, u32 generation, loff_t i_pos)
{
struct inode *inode = fat_ilookup(sb, ino, i_pos);
if (inode && generation && (inode->i_generation != generation)) {
iput(inode);
inode = NULL;
}
if (inode == NULL && MSDOS_SB(sb)->options.nfs == FAT_NFS_NOSTALE_RO) {
struct buffer_head *bh = NULL;
struct msdos_dir_entry *de ;
sector_t blocknr;
int offset;
fat_get_blknr_offset(MSDOS_SB(sb), i_pos, &blocknr, &offset);
bh = sb_bread(sb, blocknr);
if (!bh) {
fat_msg(sb, KERN_ERR,
"unable to read block(%llu) for building NFS inode",
(llu)blocknr);
return inode;
}
de = (struct msdos_dir_entry *)bh->b_data;
/* If a file is deleted on server and client is not updated
* yet, we must not build the inode upon a lookup call.
*/
if (IS_FREE(de[offset].name))
inode = NULL;
else
inode = fat_build_inode(sb, &de[offset], i_pos);
brelse(bh);
}
return inode;
}
static struct inode *fat_nfs_get_inode(struct super_block *sb,
u64 ino, u32 generation)
{
return __fat_nfs_get_inode(sb, ino, generation, 0);
}
static int
fat_encode_fh_nostale(struct inode *inode, __u32 *fh, int *lenp,
struct inode *parent)
{
int len = *lenp;
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
struct fat_fid *fid = (struct fat_fid *) fh;
loff_t i_pos;
int type = FILEID_FAT_WITHOUT_PARENT;
if (parent) {
if (len < FAT_FID_SIZE_WITH_PARENT) {
*lenp = FAT_FID_SIZE_WITH_PARENT;
return FILEID_INVALID;
}
} else {
if (len < FAT_FID_SIZE_WITHOUT_PARENT) {
*lenp = FAT_FID_SIZE_WITHOUT_PARENT;
return FILEID_INVALID;
}
}
i_pos = fat_i_pos_read(sbi, inode);
*lenp = FAT_FID_SIZE_WITHOUT_PARENT;
fid->i_gen = inode->i_generation;
fid->i_pos_low = i_pos & 0xFFFFFFFF;
fid->i_pos_hi = (i_pos >> 32) & 0xFFFF;
if (parent) {
i_pos = fat_i_pos_read(sbi, parent);
fid->parent_i_pos_hi = (i_pos >> 32) & 0xFFFF;
fid->parent_i_pos_low = i_pos & 0xFFFFFFFF;
fid->parent_i_gen = parent->i_generation;
type = FILEID_FAT_WITH_PARENT;
*lenp = FAT_FID_SIZE_WITH_PARENT;
} else {
/*
* We need to initialize this field because the fh is actually
* 12 bytes long
*/
fid->parent_i_pos_hi = 0;
}
return type;
}
/*
* Map a NFS file handle to a corresponding dentry.
* The dentry may or may not be connected to the filesystem root.
*/
static struct dentry *fat_fh_to_dentry(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
return generic_fh_to_dentry(sb, fid, fh_len, fh_type,
fat_nfs_get_inode);
}
static struct dentry *fat_fh_to_dentry_nostale(struct super_block *sb,
struct fid *fh, int fh_len,
int fh_type)
{
struct inode *inode = NULL;
struct fat_fid *fid = (struct fat_fid *)fh;
loff_t i_pos;
switch (fh_type) {
case FILEID_FAT_WITHOUT_PARENT:
if (fh_len < FAT_FID_SIZE_WITHOUT_PARENT)
return NULL;
break;
case FILEID_FAT_WITH_PARENT:
if (fh_len < FAT_FID_SIZE_WITH_PARENT)
return NULL;
break;
default:
return NULL;
}
i_pos = fid->i_pos_hi;
i_pos = (i_pos << 32) | (fid->i_pos_low);
inode = __fat_nfs_get_inode(sb, 0, fid->i_gen, i_pos);
return d_obtain_alias(inode);
}
/*
* Find the parent for a file specified by NFS handle.
* This requires that the handle contain the i_ino of the parent.
*/
static struct dentry *fat_fh_to_parent(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
return generic_fh_to_parent(sb, fid, fh_len, fh_type,
fat_nfs_get_inode);
}
static struct dentry *fat_fh_to_parent_nostale(struct super_block *sb,
struct fid *fh, int fh_len,
int fh_type)
{
struct inode *inode = NULL;
struct fat_fid *fid = (struct fat_fid *)fh;
loff_t i_pos;
if (fh_len < FAT_FID_SIZE_WITH_PARENT)
return NULL;
switch (fh_type) {
case FILEID_FAT_WITH_PARENT:
i_pos = fid->parent_i_pos_hi;
i_pos = (i_pos << 32) | (fid->parent_i_pos_low);
inode = __fat_nfs_get_inode(sb, 0, fid->parent_i_gen, i_pos);
break;
}
return d_obtain_alias(inode);
}
/*
* Rebuild the parent for a directory that is not connected
* to the filesystem root
*/
static
struct inode *fat_rebuild_parent(struct super_block *sb, int parent_logstart)
{
int search_clus, clus_to_match;
struct msdos_dir_entry *de;
struct inode *parent = NULL;
struct inode *dummy_grand_parent = NULL;
struct fat_slot_info sinfo;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
sector_t blknr = fat_clus_to_blknr(sbi, parent_logstart);
struct buffer_head *parent_bh = sb_bread(sb, blknr);
if (!parent_bh) {
fat_msg(sb, KERN_ERR,
"unable to read cluster of parent directory");
return NULL;
}
de = (struct msdos_dir_entry *) parent_bh->b_data;
clus_to_match = fat_get_start(sbi, &de[0]);
search_clus = fat_get_start(sbi, &de[1]);
dummy_grand_parent = fat_dget(sb, search_clus);
if (!dummy_grand_parent) {
dummy_grand_parent = new_inode(sb);
if (!dummy_grand_parent) {
brelse(parent_bh);
return parent;
}
dummy_grand_parent->i_ino = iunique(sb, MSDOS_ROOT_INO);
fat_fill_inode(dummy_grand_parent, &de[1]);
MSDOS_I(dummy_grand_parent)->i_pos = -1;
}
if (!fat_scan_logstart(dummy_grand_parent, clus_to_match, &sinfo))
parent = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
brelse(parent_bh);
iput(dummy_grand_parent);
return parent;
}
/*
* Find the parent for a directory that is not currently connected to
* the filesystem root.
*
* On entry, the caller holds d_inode(child_dir)->i_mutex.
*/
static struct dentry *fat_get_parent(struct dentry *child_dir)
{
struct super_block *sb = child_dir->d_sb;
struct buffer_head *bh = NULL;
struct msdos_dir_entry *de;
struct inode *parent_inode = NULL;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
if (!fat_get_dotdot_entry(d_inode(child_dir), &bh, &de)) {
int parent_logstart = fat_get_start(sbi, de);
parent_inode = fat_dget(sb, parent_logstart);
if (!parent_inode && sbi->options.nfs == FAT_NFS_NOSTALE_RO)
parent_inode = fat_rebuild_parent(sb, parent_logstart);
}
brelse(bh);
return d_obtain_alias(parent_inode);
}
const struct export_operations fat_export_ops = {
.encode_fh = generic_encode_ino32_fh,
.fh_to_dentry = fat_fh_to_dentry,
.fh_to_parent = fat_fh_to_parent,
.get_parent = fat_get_parent,
};
const struct export_operations fat_export_ops_nostale = {
.encode_fh = fat_encode_fh_nostale,
.fh_to_dentry = fat_fh_to_dentry_nostale,
.fh_to_parent = fat_fh_to_parent_nostale,
.get_parent = fat_get_parent,
};