mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
119
fs/fat/Kconfig
Normal file
119
fs/fat/Kconfig
Normal file
|
@ -0,0 +1,119 @@
|
|||
config FAT_FS
|
||||
tristate
|
||||
select NLS
|
||||
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
|
||||
<http://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.txt> 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 MSDOS_FS || VFAT_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.txt> 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.
|
||||
See <file:Documentation/filesystems/vfat.txt> for more information.
|
||||
|
||||
Enable any character sets you need in File Systems/Native Language
|
||||
Support.
|
||||
|
||||
config FAT_VIRTUAL_XATTR
|
||||
bool "Virtual xattr support for FAT filesystem"
|
||||
default y
|
||||
depends on FAT_FS
|
||||
help
|
||||
Modification of FAT filesystem for virtual xattr
|
||||
|
||||
config FAT_VIRTUAL_XATTR_SELINUX_LABEL
|
||||
string "Default string for SELinux label"
|
||||
depends on FAT_FS && FAT_VIRTUAL_XATTR
|
||||
default "u:object_r:sdcard_external:s0"
|
||||
help
|
||||
Set this to the default string for SELinux label.
|
||||
|
||||
config FAT_SUPPORT_STLOG
|
||||
bool "Enable storage log"
|
||||
default y
|
||||
depends on FAT_FS && PROC_STLOG
|
11
fs/fat/Makefile
Normal file
11
fs/fat/Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
# 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 xattr.o
|
||||
vfat-y := namei_vfat.o
|
||||
msdos-y := namei_msdos.o
|
353
fs/fat/cache.c
Normal file
353
fs/fat/cache.c
Normal file
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* 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/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/buffer_head.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 inline int fat_max_cache(struct inode *inode)
|
||||
{
|
||||
return FAT_MAX_CACHE;
|
||||
}
|
||||
|
||||
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|SLAB_MEM_SPREAD,
|
||||
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(struct inode *inode)
|
||||
{
|
||||
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(inode)) {
|
||||
MSDOS_I(inode)->nr_caches++;
|
||||
spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
|
||||
|
||||
tmp = fat_cache_alloc(inode);
|
||||
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;
|
||||
const int limit = sb->s_maxbytes >> MSDOS_SB(sb)->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 (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_bmap(struct inode *inode, sector_t sector, sector_t *phys,
|
||||
unsigned long *mapped_blocks, int create)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct msdos_sb_info *sbi = MSDOS_SB(sb);
|
||||
const unsigned long blocksize = sb->s_blocksize;
|
||||
const unsigned char blocksize_bits = sb->s_blocksize_bits;
|
||||
sector_t last_block;
|
||||
int cluster, offset;
|
||||
|
||||
*phys = 0;
|
||||
*mapped_blocks = 0;
|
||||
if ((sbi->fat_bits != 32) && (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;
|
||||
}
|
||||
|
||||
last_block = (i_size_read(inode) + (blocksize - 1)) >> blocksize_bits;
|
||||
if (sector >= last_block) {
|
||||
if (!create)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* ->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 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
*phys = 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;
|
||||
}
|
1405
fs/fat/dir.c
Normal file
1405
fs/fat/dir.c
Normal file
File diff suppressed because it is too large
Load diff
436
fs/fat/fat.h
Normal file
436
fs/fat/fat.h
Normal file
|
@ -0,0 +1,436 @@
|
|||
#ifndef _FAT_H
|
||||
#define _FAT_H
|
||||
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/ratelimit.h>
|
||||
#include <linux/msdos_fs.h>
|
||||
|
||||
#ifdef CONFIG_FAT_SUPPORT_STLOG
|
||||
#include <linux/stlog.h>
|
||||
#else
|
||||
#define ST_LOG(fmt,...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
};
|
||||
|
||||
#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 or 16) */
|
||||
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;
|
||||
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 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;
|
||||
}
|
||||
|
||||
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(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(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 (sbi->fat_bits == 32)
|
||||
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_bmap(struct inode *inode, sector_t sector, sector_t *phys,
|
||||
unsigned long *mapped_blocks, int create);
|
||||
|
||||
/* 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 timespec *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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* 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 dentry *dentry, struct iattr *attr);
|
||||
extern void fat_truncate_blocks(struct inode *inode, loff_t offset);
|
||||
extern int fat_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
||||
struct kstat *stat);
|
||||
extern int fat_file_fsync(struct file *file, loff_t start, loff_t end,
|
||||
int datasync);
|
||||
|
||||
/* fat/inode.c */
|
||||
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, void *data, int silent,
|
||||
int isvfat, 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);
|
||||
static inline unsigned long fat_dir_hash(int logstart)
|
||||
{
|
||||
return hash_32(logstart, FAT_HASH_BITS);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
__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 timespec *ts,
|
||||
__le16 __time, __le16 __date, u8 time_cs);
|
||||
extern void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec *ts,
|
||||
__le16 *time, __le16 *date, u8 *time_cs);
|
||||
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;
|
||||
|
||||
/* fat/xattr.c */
|
||||
extern int fat_setxattr(struct dentry *dentry, const char *name,
|
||||
const void *value, size_t size, int flags);
|
||||
extern ssize_t fat_getxattr(struct dentry *dentry, const char *name,
|
||||
void *value, size_t size);
|
||||
extern ssize_t fat_listxattr(struct dentry *dentry, char *list, size_t size);
|
||||
extern int fat_removexattr(struct dentry *dentry, const char *name);
|
||||
|
||||
/* helper for printk */
|
||||
typedef unsigned long long llu;
|
||||
|
||||
#endif /* !_FAT_H */
|
696
fs/fat/fatent.c
Normal file
696
fs/fat/fatent.c
Normal file
|
@ -0,0 +1,696 @@
|
|||
/*
|
||||
* Copyright (C) 2004, OGAWA Hirofumi
|
||||
* Released under GPL v2.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/msdos_fs.h>
|
||||
#include <linux/blkdev.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(entry < FAT_START_ENT || sbi->max_cluster <= 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(entry < FAT_START_ENT || sbi->max_cluster <= 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(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)
|
||||
{
|
||||
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(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);
|
||||
|
||||
mark_buffer_dirty_inode_sync(fatent->bhs[0], fatent->fat_inode);
|
||||
if (fatent->nr_bhs == 2){
|
||||
mark_buffer_dirty_inode_sync(fatent->bhs[1], fatent->fat_inode);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
mark_buffer_dirty_inode_sync(fatent->bhs[0], fatent->fat_inode);
|
||||
}
|
||||
|
||||
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);
|
||||
mark_buffer_dirty_inode_sync(fatent->bhs[0], fatent->fat_inode);
|
||||
}
|
||||
|
||||
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 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 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 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);
|
||||
|
||||
switch (sbi->fat_bits) {
|
||||
case 32:
|
||||
sbi->fatent_shift = 2;
|
||||
sbi->fatent_ops = &fat32_ops;
|
||||
break;
|
||||
case 16:
|
||||
sbi->fatent_shift = 1;
|
||||
sbi->fatent_ops = &fat16_ops;
|
||||
break;
|
||||
case 12:
|
||||
sbi->fatent_shift = -1;
|
||||
sbi->fatent_ops = &fat12_ops;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_fsinfo_dirty(struct super_block *sb)
|
||||
{
|
||||
struct msdos_sb_info *sbi = MSDOS_SB(sb);
|
||||
|
||||
if (sb->s_flags & MS_RDONLY || sbi->fat_bits != 32)
|
||||
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);
|
||||
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 (sbi->fat_bits == 12) {
|
||||
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);
|
||||
struct fatent_operations *ops = sbi->fatent_ops;
|
||||
int err, offset;
|
||||
sector_t blocknr;
|
||||
|
||||
if (entry < FAT_START_ENT || sbi->max_cluster <= entry) {
|
||||
fatent_brelse(fatent);
|
||||
fat_fs_error(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;
|
||||
}
|
||||
memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
|
||||
set_buffer_uptodate(c_bh);
|
||||
mark_buffer_dirty_inode_sync(c_bh, sbi->fat_inode);
|
||||
if (sb->s_flags & MS_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;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
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 & MS_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 & MS_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);
|
||||
|
||||
/* 128kb is the whole sectors for FAT12 and FAT16 */
|
||||
#define FAT_READA_SIZE (128 * 1024)
|
||||
|
||||
static void fat_ent_reada(struct super_block *sb, struct fat_entry *fatent,
|
||||
unsigned long reada_blocks)
|
||||
{
|
||||
struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
|
||||
sector_t blocknr;
|
||||
int i, offset;
|
||||
|
||||
ops->ent_blocknr(sb, fatent->entry, &offset, &blocknr);
|
||||
|
||||
for (i = 0; i < reada_blocks; i++)
|
||||
sb_breadahead(sb, blocknr + i);
|
||||
}
|
||||
|
||||
int fat_count_free_clusters(struct super_block *sb)
|
||||
{
|
||||
struct msdos_sb_info *sbi = MSDOS_SB(sb);
|
||||
struct fatent_operations *ops = sbi->fatent_ops;
|
||||
struct fat_entry fatent;
|
||||
unsigned long reada_blocks, reada_mask, cur_block;
|
||||
int err = 0, free;
|
||||
|
||||
lock_fat(sbi);
|
||||
if (sbi->free_clusters != -1 && sbi->free_clus_valid)
|
||||
goto out;
|
||||
|
||||
reada_blocks = FAT_READA_SIZE >> sb->s_blocksize_bits;
|
||||
reada_mask = reada_blocks - 1;
|
||||
cur_block = 0;
|
||||
|
||||
free = 0;
|
||||
fatent_init(&fatent);
|
||||
fatent_set_entry(&fatent, FAT_START_ENT);
|
||||
while (fatent.entry < sbi->max_cluster) {
|
||||
/* readahead of fat blocks */
|
||||
if ((cur_block & reada_mask) == 0) {
|
||||
unsigned long rest = sbi->fat_length - cur_block;
|
||||
fat_ent_reada(sb, &fatent, min(reada_blocks, rest));
|
||||
}
|
||||
cur_block++;
|
||||
|
||||
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));
|
||||
}
|
||||
sbi->free_clusters = free;
|
||||
sbi->free_clus_valid = 1;
|
||||
mark_fsinfo_dirty(sb);
|
||||
fatent_brelse(&fatent);
|
||||
out:
|
||||
unlock_fat(sbi);
|
||||
return err;
|
||||
}
|
468
fs/fat/file.c
Normal file
468
fs/fat/file.c
Normal file
|
@ -0,0 +1,468 @@
|
|||
/*
|
||||
* 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/time.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/backing-dev.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/fsnotify.h>
|
||||
#include <linux/security.h>
|
||||
#include "fat.h"
|
||||
|
||||
static int fat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr)
|
||||
{
|
||||
u32 attr;
|
||||
|
||||
mutex_lock(&inode->i_mutex);
|
||||
attr = fat_make_attrs(inode);
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
|
||||
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;
|
||||
mutex_lock(&inode->i_mutex);
|
||||
|
||||
/*
|
||||
* 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_fs_time(inode->i_sb);
|
||||
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->f_path.dentry, &ia);
|
||||
if (err)
|
||||
goto out_unlock_inode;
|
||||
|
||||
/* This MUST be done before doing anything irreversible... */
|
||||
err = fat_setattr(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:
|
||||
mutex_unlock(&inode->i_mutex);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
default:
|
||||
return -ENOTTY; /* Inappropriate ioctl for device */
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long fat_generic_compat_ioctl(struct file *filp, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
|
||||
{
|
||||
return fat_generic_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#endif
|
||||
|
||||
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);
|
||||
congestion_wait(BLK_RW_ASYNC, 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;
|
||||
int res, err;
|
||||
|
||||
res = generic_file_fsync(filp, start, end, datasync);
|
||||
err = sync_mapping_buffers(MSDOS_SB(inode->i_sb)->fat_inode->i_mapping);
|
||||
|
||||
return res ? res : err;
|
||||
}
|
||||
|
||||
|
||||
const struct file_operations fat_file_operations = {
|
||||
.llseek = generic_file_llseek,
|
||||
.read = new_sync_read,
|
||||
.write = new_sync_write,
|
||||
.read_iter = generic_file_read_iter,
|
||||
.write_iter = generic_file_write_iter,
|
||||
.mmap = generic_file_mmap,
|
||||
.release = fat_file_release,
|
||||
.unlocked_ioctl = fat_generic_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = fat_generic_compat_ioctl,
|
||||
#endif
|
||||
.fsync = fat_file_fsync,
|
||||
.splice_read = generic_file_splice_read,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
|
||||
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 = sync_mapping_buffers(mapping);
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
|
||||
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_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
generic_fillattr(inode, stat);
|
||||
stat->blksize = MSDOS_SB(inode->i_sb)->cluster_size;
|
||||
|
||||
if (MSDOS_SB(inode->i_sb)->options.nfs == FAT_NFS_NOSTALE_RO) {
|
||||
/* Use i_pos for ino. This is used as fileid of nfs. */
|
||||
stat->ino = fat_i_pos_read(MSDOS_SB(inode->i_sb), inode);
|
||||
}
|
||||
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 msdos_sb_info *sbi, struct inode *inode)
|
||||
{
|
||||
umode_t allow_utime = sbi->options.allow_utime;
|
||||
|
||||
if (!uid_eq(current_fsuid(), inode->i_uid)) {
|
||||
if (in_group_p(inode->i_gid))
|
||||
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 dentry *dentry, struct iattr *attr)
|
||||
{
|
||||
struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
|
||||
struct inode *inode = dentry->d_inode;
|
||||
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(sbi, inode))
|
||||
attr->ia_valid &= ~TIMES_SET_FLAGS;
|
||||
}
|
||||
|
||||
error = inode_change_ok(inode, 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(attr->ia_uid, sbi->options.fs_uid))) ||
|
||||
((attr->ia_valid & ATTR_GID) &&
|
||||
(!gid_eq(attr->ia_gid, 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) {
|
||||
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(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,
|
||||
#ifdef CONFIG_FAT_VIRTUAL_XATTR
|
||||
.setxattr = fat_setxattr,
|
||||
.getxattr = fat_getxattr,
|
||||
.listxattr = fat_listxattr,
|
||||
.removexattr = fat_removexattr,
|
||||
#endif
|
||||
};
|
1864
fs/fat/inode.c
Normal file
1864
fs/fat/inode.c
Normal file
File diff suppressed because it is too large
Load diff
302
fs/fat/misc.c
Normal file
302
fs/fat/misc.c
Normal file
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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 <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/time.h>
|
||||
#include "fat.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;
|
||||
struct block_device *bdev = sb->s_bdev;
|
||||
dev_t bd_dev = bdev ? bdev->bd_dev : 0;
|
||||
|
||||
if (report) {
|
||||
va_start(args, fmt);
|
||||
vaf.fmt = fmt;
|
||||
vaf.va = &args;
|
||||
printk(KERN_ERR "FAT-fs (%s[%d:%d]): error, %pV\n",
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf);
|
||||
|
||||
if (opts->errors == FAT_ERRORS_RO && !(sb->s_flags & MS_RDONLY))
|
||||
ST_LOG("FAT-fs (%s[%d:%d]): error, %pV\n",
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
if (opts->errors == FAT_ERRORS_PANIC)
|
||||
panic("FAT-fs (%s[%d:%d]): fs panic from previous error\n",
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev));
|
||||
else if (opts->errors == FAT_ERRORS_RO && !(sb->s_flags & MS_RDONLY)) {
|
||||
sb->s_flags |= MS_RDONLY;
|
||||
printk(KERN_ERR "FAT-fs (%s[%d:%d]): Filesystem has been "
|
||||
"set read-only\n",
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev));
|
||||
|
||||
ST_LOG("FAT-fs (%s[%d:%d]): Filesystem has been set read-only\n",
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev));
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__fat_fs_error);
|
||||
|
||||
/**
|
||||
* fat_msg() - print preformated FAT specific messages. Every thing what is
|
||||
* not fat_fs_error() should be fat_msg().
|
||||
*/
|
||||
void fat_msg(struct super_block *sb, const char *level, const char *fmt, ...)
|
||||
{
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
struct block_device *bdev = sb->s_bdev;
|
||||
dev_t bd_dev = bdev ? bdev->bd_dev : 0;
|
||||
|
||||
va_start(args, fmt);
|
||||
vaf.fmt = fmt;
|
||||
vaf.va = &args;
|
||||
if (!strncmp(level, KERN_ERR, sizeof(KERN_ERR)))
|
||||
printk_ratelimited("%sFAT-fs (%s[%d:%d]): %pV\n", level,
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf);
|
||||
else
|
||||
printk("%sFAT-fs (%s[%d:%d]): %pV\n", level,
|
||||
sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &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 (sbi->fat_bits != 32)
|
||||
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_sync(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(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 time_t 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,
|
||||
};
|
||||
|
||||
/* 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 timespec *ts,
|
||||
__le16 __time, __le16 __date, u8 time_cs)
|
||||
{
|
||||
u16 time = le16_to_cpu(__time), date = le16_to_cpu(__date);
|
||||
time_t second, 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 += (year * 365 + leap_day
|
||||
+ days_in_year[month] + day
|
||||
+ DAYS_DELTA) * SECS_PER_DAY;
|
||||
|
||||
if (!sbi->options.tz_set)
|
||||
second += sys_tz.tz_minuteswest * SECS_PER_MIN;
|
||||
else
|
||||
second -= sbi->options.time_offset * SECS_PER_MIN;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert linear UNIX date to a FAT time/date pair. */
|
||||
void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec *ts,
|
||||
__le16 *time, __le16 *date, u8 *time_cs)
|
||||
{
|
||||
struct tm tm;
|
||||
time_to_tm(ts->tv_sec,
|
||||
(sbi->options.tz_set ? sbi->options.time_offset :
|
||||
-sys_tz.tz_minuteswest) * SECS_PER_MIN, &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);
|
||||
|
||||
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], WRITE);
|
||||
|
||||
for (i = 0; i < nr_bhs; i++) {
|
||||
wait_on_buffer(bhs[i]);
|
||||
if (!err && !buffer_uptodate(bhs[i]))
|
||||
err = -EIO;
|
||||
}
|
||||
return err;
|
||||
}
|
686
fs/fat/namei_msdos.c
Normal file
686
fs/fat/namei_msdos.c
Normal file
|
@ -0,0 +1,686 @@
|
|||
/*
|
||||
* 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/time.h>
|
||||
#include <linux/buffer_head.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(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 *parent, const struct dentry *dentry,
|
||||
unsigned int len, const char *str, const struct qstr *name)
|
||||
{
|
||||
struct fat_mount_options *options = &MSDOS_SB(parent->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 timespec *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;
|
||||
|
||||
dir->i_ctime = dir->i_mtime = *ts;
|
||||
if (IS_DIRSYNC(dir))
|
||||
(void)fat_sync_inode(dir);
|
||||
else
|
||||
mark_inode_dirty(dir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***** Create a file */
|
||||
static int msdos_create(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 timespec 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_SEC;
|
||||
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;
|
||||
}
|
||||
inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
|
||||
/* 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 = dentry->d_inode;
|
||||
struct fat_slot_info sinfo;
|
||||
int err;
|
||||
|
||||
mutex_lock(&MSDOS_SB(sb)->s_lock);
|
||||
/*
|
||||
* Check whether the directory is not in use, then check
|
||||
* whether it is empty.
|
||||
*/
|
||||
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;
|
||||
drop_nlink(dir);
|
||||
|
||||
clear_nlink(inode);
|
||||
inode->i_ctime = CURRENT_TIME_SEC;
|
||||
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 int msdos_mkdir(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 timespec 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_SEC;
|
||||
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);
|
||||
inode->i_mtime = inode->i_atime = inode->i_ctime = ts;
|
||||
/* 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 0;
|
||||
|
||||
out_free:
|
||||
fat_free_clusters(dir, cluster);
|
||||
out:
|
||||
mutex_unlock(&MSDOS_SB(sb)->s_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
/***** Unlink a file */
|
||||
static int msdos_unlink(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
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);
|
||||
inode->i_ctime = CURRENT_TIME_SEC;
|
||||
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 timespec 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 = old_dentry->d_inode;
|
||||
new_inode = new_dentry->d_inode;
|
||||
|
||||
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);
|
||||
|
||||
old_dir->i_version++;
|
||||
old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME_SEC;
|
||||
if (IS_DIRSYNC(old_dir))
|
||||
(void)fat_sync_inode(old_dir);
|
||||
else
|
||||
mark_inode_dirty(old_dir);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ts = CURRENT_TIME_SEC;
|
||||
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;
|
||||
}
|
||||
new_dir->i_version++;
|
||||
|
||||
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);
|
||||
mark_buffer_dirty_inode(dotdot_bh, old_inode);
|
||||
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;
|
||||
old_dir->i_version++;
|
||||
old_dir->i_ctime = old_dir->i_mtime = ts;
|
||||
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);
|
||||
new_inode->i_ctime = ts;
|
||||
}
|
||||
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);
|
||||
mark_buffer_dirty_inode(dotdot_bh, old_inode);
|
||||
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 inode *old_dir, struct dentry *old_dentry,
|
||||
struct inode *new_dir, struct dentry *new_dentry)
|
||||
{
|
||||
struct super_block *sb = old_dir->i_sb;
|
||||
unsigned char old_msdos_name[MSDOS_NAME], new_msdos_name[MSDOS_NAME];
|
||||
int err, is_hid;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static void setup(struct super_block *sb)
|
||||
{
|
||||
MSDOS_SB(sb)->dir_ops = &msdos_dir_inode_operations;
|
||||
sb->s_d_op = &msdos_dentry_operations;
|
||||
sb->s_flags |= MS_NOATIME;
|
||||
}
|
||||
|
||||
static int msdos_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
return fat_fill_super(sb, data, silent, 0, setup);
|
||||
}
|
||||
|
||||
static struct dentry *msdos_mount(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name,
|
||||
void *data)
|
||||
{
|
||||
return mount_bdev(fs_type, flags, dev_name, data, msdos_fill_super);
|
||||
}
|
||||
|
||||
static struct file_system_type msdos_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "msdos",
|
||||
.mount = msdos_mount,
|
||||
.kill_sb = kill_block_super,
|
||||
.fs_flags = FS_REQUIRES_DEV,
|
||||
};
|
||||
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)
|
1097
fs/fat/namei_vfat.c
Normal file
1097
fs/fat/namei_vfat.c
Normal file
File diff suppressed because it is too large
Load diff
301
fs/fat/nfs.c
Normal file
301
fs/fat/nfs.c
Normal file
|
@ -0,0 +1,301 @@
|
|||
/* fs/fat/nfs.c
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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 child_dir->d_inode->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(child_dir->d_inode, &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 = {
|
||||
.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,
|
||||
};
|
35
fs/fat/xattr.c
Normal file
35
fs/fat/xattr.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/dcache.h>
|
||||
#include "fat.h"
|
||||
|
||||
#ifndef CONFIG_FAT_VIRTUAL_XATTR_SELINUX_LABEL
|
||||
#define CONFIG_FAT_VIRTUAL_XATTR_SELINUX_LABEL ("undefined")
|
||||
#endif
|
||||
|
||||
static const char default_xattr[] = CONFIG_FAT_VIRTUAL_XATTR_SELINUX_LABEL;
|
||||
|
||||
int fat_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) {
|
||||
if (!name || strcmp(name, "security.selinux"))
|
||||
return -EOPNOTSUPP;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t fat_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) {
|
||||
if (!name || strcmp(name, "security.selinux"))
|
||||
return -EOPNOTSUPP;
|
||||
if (size > strlen(default_xattr)+1 && value)
|
||||
strcpy(value, default_xattr);
|
||||
return strlen(default_xattr);
|
||||
}
|
||||
|
||||
ssize_t fat_listxattr(struct dentry *dentry, char *list, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fat_removexattr(struct dentry *dentry, const char *name) {
|
||||
if (!name || strcmp(name, "security.selinux"))
|
||||
return -EOPNOTSUPP;
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue