#include <linux/iversion.h>
#include <linux/namei.h>
#include <linux/slab.h>
#include <linux/buffer_head.h>
#include <linux/nls.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
static inline unsigned long exfat_d_version(struct dentry *dentry)
{
return (unsigned long) dentry->d_fsdata;
}
static inline void exfat_d_version_set(struct dentry *dentry,
unsigned long version)
{
dentry->d_fsdata = (void *) version;
}
static int exfat_d_revalidate(struct inode *dir, const struct qstr *name,
struct dentry *dentry, unsigned int flags)
{
if (flags & LOOKUP_RCU)
return -ECHILD;
if (d_really_is_positive(dentry))
return 1;
if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))
return 0;
return inode_eq_iversion(dir, exfat_d_version(dentry));
}
static unsigned int exfat_striptail_len(unsigned int len, const char *name,
bool keep_last_dots)
{
if (!keep_last_dots) {
while (len && name[len - 1] == '.')
len--;
}
return len;
}
static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr)
{
struct super_block *sb = dentry->d_sb;
struct nls_table *t = EXFAT_SB(sb)->nls_io;
const unsigned char *name = qstr->name;
unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
EXFAT_SB(sb)->options.keep_last_dots);
unsigned long hash = init_name_hash(dentry);
int i, charlen;
wchar_t c;
for (i = 0; i < len; i += charlen) {
charlen = t->char2uni(&name[i], len - i, &c);
if (charlen < 0)
return charlen;
hash = partial_name_hash(exfat_toupper(sb, c), hash);
}
qstr->hash = end_name_hash(hash);
return 0;
}
static int exfat_d_cmp(const struct dentry *dentry, unsigned int len,
const char *str, const struct qstr *name)
{
struct super_block *sb = dentry->d_sb;
struct nls_table *t = EXFAT_SB(sb)->nls_io;
unsigned int alen = exfat_striptail_len(name->len, name->name,
EXFAT_SB(sb)->options.keep_last_dots);
unsigned int blen = exfat_striptail_len(len, str,
EXFAT_SB(sb)->options.keep_last_dots);
wchar_t c1, c2;
int charlen, i;
if (alen != blen)
return 1;
for (i = 0; i < len; i += charlen) {
charlen = t->char2uni(&name->name[i], alen - i, &c1);
if (charlen < 0)
return 1;
if (charlen != t->char2uni(&str[i], blen - i, &c2))
return 1;
if (exfat_toupper(sb, c1) != exfat_toupper(sb, c2))
return 1;
}
return 0;
}
const struct dentry_operations exfat_dentry_ops = {
.d_revalidate = exfat_d_revalidate,
.d_hash = exfat_d_hash,
.d_compare = exfat_d_cmp,
};
static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr)
{
struct super_block *sb = dentry->d_sb;
const unsigned char *name = qstr->name;
unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
EXFAT_SB(sb)->options.keep_last_dots);
unsigned long hash = init_name_hash(dentry);
int i, charlen;
unicode_t u;
for (i = 0; i < len; i += charlen) {
charlen = utf8_to_utf32(&name[i], len - i, &u);
if (charlen < 0)
return charlen;
hash = partial_name_hash(u <= 0xFFFF ? exfat_toupper(sb, u) : u,
hash);
}
qstr->hash = end_name_hash(hash);
return 0;
}
static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len,
const char *str, const struct qstr *name)
{
struct super_block *sb = dentry->d_sb;
unsigned int alen = exfat_striptail_len(name->len, name->name,
EXFAT_SB(sb)->options.keep_last_dots);
unsigned int blen = exfat_striptail_len(len, str,
EXFAT_SB(sb)->options.keep_last_dots);
unicode_t u_a, u_b;
int charlen, i;
if (alen != blen)
return 1;
for (i = 0; i < alen; i += charlen) {
charlen = utf8_to_utf32(&name->name[i], alen - i, &u_a);
if (charlen < 0)
return 1;
if (charlen != utf8_to_utf32(&str[i], blen - i, &u_b))
return 1;
if (u_a <= 0xFFFF && u_b <= 0xFFFF) {
if (exfat_toupper(sb, u_a) != exfat_toupper(sb, u_b))
return 1;
} else {
if (u_a != u_b)
return 1;
}
}
return 0;
}
const struct dentry_operations exfat_utf8_dentry_ops = {
.d_revalidate = exfat_d_revalidate,
.d_hash = exfat_utf8_d_hash,
.d_compare = exfat_utf8_d_cmp,
};
static int exfat_search_empty_slot(struct super_block *sb,
struct exfat_hint_femp *hint_femp, struct exfat_chain *p_dir,
int num_entries, struct exfat_entry_set_cache *es)
{
int i, dentry, ret;
int dentries_per_clu;
struct exfat_chain clu;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
int total_entries = EXFAT_CLU_TO_DEN(p_dir->size, sbi);
dentries_per_clu = sbi->dentries_per_clu;
if (hint_femp->eidx != EXFAT_HINT_NONE) {
dentry = hint_femp->eidx;
if (dentry + hint_femp->count == total_entries &&
num_entries > hint_femp->count)
return -ENOSPC;
hint_femp->eidx = EXFAT_HINT_NONE;
exfat_chain_dup(&clu, &hint_femp->cur);
} else {
exfat_chain_dup(&clu, p_dir);
dentry = 0;
}
while (dentry + num_entries <= total_entries &&
clu.dir != EXFAT_EOF_CLUSTER) {
i = dentry & (dentries_per_clu - 1);
ret = exfat_get_empty_dentry_set(es, sb, &clu, i, num_entries);
if (ret < 0)
return ret;
else if (ret == 0)
return dentry;
dentry += ret;
i += ret;
while (i >= dentries_per_clu) {
if (clu.flags == ALLOC_NO_FAT_CHAIN) {
if (--clu.size > 0)
clu.dir++;
else
clu.dir = EXFAT_EOF_CLUSTER;
} else {
if (exfat_get_next_cluster(sb, &clu.dir))
return -EIO;
}
i -= dentries_per_clu;
}
}
hint_femp->eidx = dentry;
hint_femp->count = 0;
if (dentry == total_entries || clu.dir == EXFAT_EOF_CLUSTER)
exfat_chain_set(&hint_femp->cur, EXFAT_EOF_CLUSTER, 0,
clu.flags);
else
hint_femp->cur = clu;
return -ENOSPC;
}
static int exfat_check_max_dentries(struct inode *inode)
{
if (EXFAT_B_TO_DEN(i_size_read(inode)) >= MAX_EXFAT_DENTRIES) {
return -ENOSPC;
}
return 0;
}
static int exfat_find_empty_entry(struct inode *inode,
struct exfat_chain *p_dir, int num_entries,
struct exfat_entry_set_cache *es)
{
int dentry;
unsigned int ret, last_clu;
loff_t size = 0;
struct exfat_chain clu;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
struct exfat_hint_femp hint_femp;
hint_femp.eidx = EXFAT_HINT_NONE;
if (ei->hint_femp.eidx != EXFAT_HINT_NONE) {
hint_femp = ei->hint_femp;
ei->hint_femp.eidx = EXFAT_HINT_NONE;
}
exfat_chain_set(p_dir, ei->start_clu,
EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir,
num_entries, es)) < 0) {
if (dentry != -ENOSPC)
return dentry;
if (exfat_check_max_dentries(inode))
return -ENOSPC;
if (ei->start_clu != EXFAT_EOF_CLUSTER) {
if (exfat_find_last_cluster(sb, p_dir, &last_clu))
return -EIO;
exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags);
} else {
exfat_chain_set(&clu, EXFAT_EOF_CLUSTER, 0,
ALLOC_NO_FAT_CHAIN);
}
ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode));
if (ret)
return ret;
if (exfat_zeroed_cluster(inode, clu.dir))
return -EIO;
if (ei->start_clu == EXFAT_EOF_CLUSTER) {
ei->start_clu = clu.dir;
p_dir->dir = clu.dir;
hint_femp.eidx = 0;
}
if (clu.flags != p_dir->flags) {
exfat_chain_cont_cluster(sb, p_dir->dir, p_dir->size);
p_dir->flags = ALLOC_FAT_CHAIN;
hint_femp.cur.flags = ALLOC_FAT_CHAIN;
}
if (clu.flags == ALLOC_FAT_CHAIN)
if (exfat_ent_set(sb, last_clu, clu.dir))
return -EIO;
if (hint_femp.cur.dir == EXFAT_EOF_CLUSTER)
exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags);
hint_femp.count += sbi->dentries_per_clu;
hint_femp.cur.size++;
p_dir->size++;
size = EXFAT_CLU_TO_B(p_dir->size, sbi);
i_size_write(inode, size);
ei->valid_size += sbi->cluster_size;
ei->flags = p_dir->flags;
inode->i_blocks += sbi->cluster_size >> 9;
}
p_dir->dir = exfat_sector_to_cluster(sbi, es->bh[0]->b_blocknr);
p_dir->size -= dentry / sbi->dentries_per_clu;
return dentry & (sbi->dentries_per_clu - 1);
}
static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
struct exfat_uni_name *p_uniname, int lookup)
{
int namelen;
int lossy = NLS_NAME_NO_LOSSY;
struct super_block *sb = inode->i_sb;
int pathlen = strlen(path);
namelen = exfat_striptail_len(pathlen, path, false);
if (EXFAT_SB(sb)->options.keep_last_dots) {
if (!lookup && (namelen < pathlen))
return -EINVAL;
namelen = pathlen;
}
if (!namelen)
return -ENOENT;
if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
return -ENAMETOOLONG;
namelen = exfat_nls_to_utf16(sb, path, namelen, p_uniname,
&lossy);
if (namelen < 0)
return namelen;
if ((lossy && !lookup) || !namelen)
return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL;
return 0;
}
static inline int exfat_resolve_path(struct inode *inode,
const unsigned char *path, struct exfat_uni_name *uni)
{
return __exfat_resolve_path(inode, path, uni, 0);
}
static inline int exfat_resolve_path_for_lookup(struct inode *inode,
const unsigned char *path, struct exfat_uni_name *uni)
{
return __exfat_resolve_path(inode, path, uni, 1);
}
static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info)
{
return ((loff_t) info->dir.dir << 32) | (info->entry & 0xffffffff);
}
static int exfat_add_entry(struct inode *inode, const char *path,
unsigned int type, struct exfat_dir_entry *info)
{
int ret, dentry, num_entries;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_uni_name uniname;
struct exfat_chain clu;
struct timespec64 ts = current_time(inode);
struct exfat_entry_set_cache es;
int clu_size = 0;
unsigned int start_clu = EXFAT_FREE_CLUSTER;
ret = exfat_resolve_path(inode, path, &uniname);
if (ret)
goto out;
num_entries = exfat_calc_num_entries(&uniname);
if (num_entries < 0) {
ret = num_entries;
goto out;
}
dentry = exfat_find_empty_entry(inode, &info->dir, num_entries, &es);
if (dentry < 0) {
ret = dentry;
goto out;
}
if (type == TYPE_DIR && !sbi->options.zero_size_dir) {
ret = exfat_alloc_new_dir(inode, &clu);
if (ret) {
exfat_put_dentry_set(&es, false);
goto out;
}
start_clu = clu.dir;
clu_size = sbi->cluster_size;
}
exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts);
exfat_init_ext_entry(&es, num_entries, &uniname);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (ret)
goto out;
info->entry = dentry;
info->flags = ALLOC_NO_FAT_CHAIN;
info->type = type;
if (type == TYPE_FILE) {
info->attr = EXFAT_ATTR_ARCHIVE;
info->start_clu = EXFAT_EOF_CLUSTER;
info->size = 0;
info->num_subdirs = 0;
} else {
info->attr = EXFAT_ATTR_SUBDIR;
if (sbi->options.zero_size_dir)
info->start_clu = EXFAT_EOF_CLUSTER;
else
info->start_clu = start_clu;
info->size = clu_size;
info->num_subdirs = EXFAT_MIN_SUBDIR;
}
info->valid_size = info->size;
memset(&info->crtime, 0, sizeof(info->crtime));
memset(&info->mtime, 0, sizeof(info->mtime));
memset(&info->atime, 0, sizeof(info->atime));
out:
return ret;
}
static int exfat_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
struct exfat_dir_entry info;
loff_t i_pos;
int err;
loff_t size = i_size_read(dir);
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
mutex_lock(&EXFAT_SB(sb)->s_lock);
exfat_set_volume_dirty(sb);
err = exfat_add_entry(dir, dentry->d_name.name, TYPE_FILE, &info);
if (err)
goto unlock;
inode_inc_iversion(dir);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
if (IS_DIRSYNC(dir) && size != i_size_read(dir))
exfat_sync_inode(dir);
else
mark_inode_dirty(dir);
i_pos = exfat_make_i_pos(&info);
inode = exfat_build_inode(sb, &info, i_pos);
err = PTR_ERR_OR_ZERO(inode);
if (err)
goto unlock;
inode_inc_iversion(inode);
EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
d_instantiate(dentry, inode);
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return err;
}
static int exfat_find(struct inode *dir, struct qstr *qname,
struct exfat_dir_entry *info)
{
int ret, dentry, count;
struct exfat_chain cdir;
struct exfat_uni_name uni_name;
struct super_block *sb = dir->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(dir);
struct exfat_dentry *ep, *ep2;
struct exfat_entry_set_cache es;
struct exfat_hint hint_opt;
if (qname->len == 0)
return -ENOENT;
ret = exfat_resolve_path_for_lookup(dir, qname->name, &uni_name);
if (ret)
return ret;
exfat_chain_set(&cdir, ei->start_clu,
EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags);
if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) {
ei->hint_stat.clu = cdir.dir;
ei->hint_stat.eidx = 0;
ei->version = (inode_peek_iversion_raw(dir) & 0xffffffff);
ei->hint_femp.eidx = EXFAT_HINT_NONE;
}
dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, &hint_opt);
if (dentry < 0)
return dentry;
cdir.dir = hint_opt.clu;
if (cdir.flags & ALLOC_NO_FAT_CHAIN)
cdir.size -= dentry / sbi->dentries_per_clu;
dentry = hint_opt.eidx;
info->dir = cdir;
info->entry = dentry;
info->num_subdirs = 0;
if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES))
return -EIO;
ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
info->type = exfat_get_entry_type(ep);
info->attr = le16_to_cpu(ep->dentry.file.attr);
info->size = le64_to_cpu(ep2->dentry.stream.valid_size);
info->valid_size = le64_to_cpu(ep2->dentry.stream.valid_size);
info->size = le64_to_cpu(ep2->dentry.stream.size);
if (unlikely(EXFAT_B_TO_CLU_ROUND_UP(info->size, sbi) > sbi->used_clusters)) {
exfat_fs_error(sb, "data size is invalid(%lld)", info->size);
return -EIO;
}
info->start_clu = le32_to_cpu(ep2->dentry.stream.start_clu);
if (!is_valid_cluster(sbi, info->start_clu) && info->size) {
exfat_warn(sb, "start_clu is invalid cluster(0x%x)",
info->start_clu);
info->size = 0;
info->valid_size = 0;
}
if (info->valid_size > info->size) {
exfat_warn(sb, "valid_size(%lld) is greater than size(%lld)",
info->valid_size, info->size);
info->valid_size = info->size;
}
if (info->size == 0) {
info->flags = ALLOC_NO_FAT_CHAIN;
info->start_clu = EXFAT_EOF_CLUSTER;
} else
info->flags = ep2->dentry.stream.flags;
exfat_get_entry_time(sbi, &info->crtime,
ep->dentry.file.create_tz,
ep->dentry.file.create_time,
ep->dentry.file.create_date,
ep->dentry.file.create_time_cs);
exfat_get_entry_time(sbi, &info->mtime,
ep->dentry.file.modify_tz,
ep->dentry.file.modify_time,
ep->dentry.file.modify_date,
ep->dentry.file.modify_time_cs);
exfat_get_entry_time(sbi, &info->atime,
ep->dentry.file.access_tz,
ep->dentry.file.access_time,
ep->dentry.file.access_date,
0);
exfat_put_dentry_set(&es, false);
if (ei->start_clu == EXFAT_FREE_CLUSTER) {
exfat_fs_error(sb,
"non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)",
i_size_read(dir), ei->dir.dir, ei->entry);
return -EIO;
}
if (info->type == TYPE_DIR) {
exfat_chain_set(&cdir, info->start_clu,
EXFAT_B_TO_CLU(info->size, sbi), info->flags);
count = exfat_count_dir_entries(sb, &cdir);
if (count < 0)
return -EIO;
info->num_subdirs = count + EXFAT_MIN_SUBDIR;
}
return 0;
}
static int exfat_d_anon_disconn(struct dentry *dentry)
{
return IS_ROOT(dentry) && (dentry->d_flags & DCACHE_DISCONNECTED);
}
static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
struct dentry *alias;
struct exfat_dir_entry info;
int err;
loff_t i_pos;
mode_t i_mode;
mutex_lock(&EXFAT_SB(sb)->s_lock);
err = exfat_find(dir, &dentry->d_name, &info);
if (err) {
if (err == -ENOENT) {
inode = NULL;
goto out;
}
goto unlock;
}
i_pos = exfat_make_i_pos(&info);
inode = exfat_build_inode(sb, &info, i_pos);
err = PTR_ERR_OR_ZERO(inode);
if (err)
goto unlock;
i_mode = inode->i_mode;
alias = d_find_alias(inode);
if (alias && alias->d_parent == dentry->d_parent &&
!exfat_d_anon_disconn(alias)) {
if (d_unhashed(alias)) {
WARN_ON(alias->d_name.hash_len !=
dentry->d_name.hash_len);
exfat_info(sb, "rehashed a dentry(%p) in read lookup",
alias);
d_drop(dentry);
d_rehash(alias);
} else if (!S_ISDIR(i_mode)) {
d_move(alias, dentry);
}
iput(inode);
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return alias;
}
dput(alias);
out:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
if (!inode)
exfat_d_version_set(dentry, inode_query_iversion(dir));
return d_splice_alias(inode, dentry);
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return ERR_PTR(err);
}
static int exfat_unlink(struct inode *dir, struct dentry *dentry)
{
struct super_block *sb = dir->i_sb;
struct inode *inode = dentry->d_inode;
struct exfat_inode_info *ei = EXFAT_I(inode);
struct exfat_entry_set_cache es;
int err = 0;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
mutex_lock(&EXFAT_SB(sb)->s_lock);
if (ei->dir.dir == DIR_DELETED) {
exfat_err(sb, "abnormal access to deleted dentry");
err = -ENOENT;
goto unlock;
}
err = exfat_get_dentry_set_by_ei(&es, sb, ei);
if (err) {
err = -EIO;
goto unlock;
}
exfat_set_volume_dirty(sb);
exfat_remove_entries(inode, &es, ES_IDX_FILE);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (err)
goto unlock;
ei->dir.dir = DIR_DELETED;
inode_inc_iversion(dir);
simple_inode_init_ts(dir);
exfat_truncate_inode_atime(dir);
mark_inode_dirty(dir);
clear_nlink(inode);
simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
exfat_unhash_inode(inode);
exfat_d_version_set(dentry, inode_query_iversion(dir));
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return err;
}
static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
struct exfat_dir_entry info;
loff_t i_pos;
int err;
loff_t size = i_size_read(dir);
if (unlikely(exfat_forced_shutdown(sb)))
return ERR_PTR(-EIO);
mutex_lock(&EXFAT_SB(sb)->s_lock);
exfat_set_volume_dirty(sb);
err = exfat_add_entry(dir, dentry->d_name.name, TYPE_DIR, &info);
if (err)
goto unlock;
inode_inc_iversion(dir);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
if (IS_DIRSYNC(dir) && size != i_size_read(dir))
exfat_sync_inode(dir);
else
mark_inode_dirty(dir);
inc_nlink(dir);
i_pos = exfat_make_i_pos(&info);
inode = exfat_build_inode(sb, &info, i_pos);
err = PTR_ERR_OR_ZERO(inode);
if (err)
goto unlock;
inode_inc_iversion(inode);
EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
d_instantiate(dentry, inode);
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return ERR_PTR(err);
}
static int exfat_check_dir_empty(struct super_block *sb,
struct exfat_chain *p_dir)
{
int i, dentries_per_clu;
unsigned int type;
unsigned int clu_count = 0;
struct exfat_chain clu;
struct exfat_dentry *ep;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct buffer_head *bh;
dentries_per_clu = sbi->dentries_per_clu;
if (p_dir->dir == EXFAT_EOF_CLUSTER)
return 0;
exfat_chain_dup(&clu, p_dir);
while (clu.dir != EXFAT_EOF_CLUSTER) {
for (i = 0; i < dentries_per_clu; i++) {
ep = exfat_get_dentry(sb, &clu, i, &bh);
if (!ep)
return -EIO;
type = exfat_get_entry_type(ep);
brelse(bh);
if (type == TYPE_UNUSED)
return 0;
if (type != TYPE_FILE && type != TYPE_DIR)
continue;
return -ENOTEMPTY;
}
if (clu.flags == ALLOC_NO_FAT_CHAIN) {
if (--clu.size > 0)
clu.dir++;
else
clu.dir = EXFAT_EOF_CLUSTER;
} else {
if (exfat_get_next_cluster(sb, &(clu.dir)))
return -EIO;
if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi)))
break;
}
}
return 0;
}
static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
struct exfat_chain clu_to_free;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
struct exfat_entry_set_cache es;
int err;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
if (ei->dir.dir == DIR_DELETED) {
exfat_err(sb, "abnormal access to deleted dentry");
err = -ENOENT;
goto unlock;
}
exfat_chain_set(&clu_to_free, ei->start_clu,
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi), ei->flags);
err = exfat_check_dir_empty(sb, &clu_to_free);
if (err) {
if (err == -EIO)
exfat_err(sb, "failed to exfat_check_dir_empty : err(%d)",
err);
goto unlock;
}
err = exfat_get_dentry_set_by_ei(&es, sb, ei);
if (err) {
err = -EIO;
goto unlock;
}
exfat_set_volume_dirty(sb);
exfat_remove_entries(inode, &es, ES_IDX_FILE);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir));
if (err)
goto unlock;
ei->dir.dir = DIR_DELETED;
inode_inc_iversion(dir);
simple_inode_init_ts(dir);
exfat_truncate_inode_atime(dir);
if (IS_DIRSYNC(dir))
exfat_sync_inode(dir);
else
mark_inode_dirty(dir);
drop_nlink(dir);
clear_nlink(inode);
simple_inode_init_ts(inode);
exfat_truncate_inode_atime(inode);
exfat_unhash_inode(inode);
exfat_d_version_set(dentry, inode_query_iversion(dir));
unlock:
mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
return err;
}
static int exfat_rename_file(struct inode *parent_inode,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
int ret, num_new_entries;
struct exfat_dentry *epold, *epnew;
struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache old_es, new_es;
int sync = IS_DIRSYNC(parent_inode);
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
return num_new_entries;
ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
if (ret) {
ret = -EIO;
return ret;
}
epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
if (old_es.num_entries < num_new_entries) {
int newentry;
struct exfat_chain dir;
newentry = exfat_find_empty_entry(parent_inode, &dir,
num_new_entries, &new_es);
if (newentry < 0) {
ret = newentry;
goto put_old_es;
}
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_FILE);
*epnew = *epold;
if (exfat_get_entry_type(epnew) == TYPE_FILE) {
epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE);
ei->attr |= EXFAT_ATTR_ARCHIVE;
}
epold = exfat_get_dentry_cached(&old_es, ES_IDX_STREAM);
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epold;
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
ret = exfat_put_dentry_set(&new_es, sync);
if (ret)
goto put_old_es;
exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
ei->dir = dir;
ei->entry = newentry;
} else {
if (exfat_get_entry_type(epold) == TYPE_FILE) {
epold->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE);
ei->attr |= EXFAT_ATTR_ARCHIVE;
}
exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
exfat_init_ext_entry(&old_es, num_new_entries, p_uniname);
}
return exfat_put_dentry_set(&old_es, sync);
put_old_es:
exfat_put_dentry_set(&old_es, false);
return ret;
}
static int exfat_move_file(struct inode *parent_inode,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
int ret, newentry, num_new_entries;
struct exfat_dentry *epmov, *epnew;
struct exfat_entry_set_cache mov_es, new_es;
struct exfat_chain newdir;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
return num_new_entries;
ret = exfat_get_dentry_set_by_ei(&mov_es, parent_inode->i_sb, ei);
if (ret)
return -EIO;
newentry = exfat_find_empty_entry(parent_inode, &newdir,
num_new_entries, &new_es);
if (newentry < 0) {
ret = newentry;
goto put_mov_es;
}
epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE);
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_FILE);
*epnew = *epmov;
if (exfat_get_entry_type(epnew) == TYPE_FILE) {
epnew->dentry.file.attr |= cpu_to_le16(EXFAT_ATTR_ARCHIVE);
ei->attr |= EXFAT_ATTR_ARCHIVE;
}
epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_STREAM);
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epmov;
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
ei->dir = newdir;
ei->entry = newentry;
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
if (ret)
goto put_mov_es;
return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es:
exfat_put_dentry_set(&mov_es, false);
return ret;
}
static int __exfat_rename(struct inode *old_parent_inode,
struct exfat_inode_info *ei, struct inode *new_parent_inode,
struct dentry *new_dentry)
{
int ret;
struct exfat_uni_name uni_name;
struct super_block *sb = old_parent_inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
const unsigned char *new_path = new_dentry->d_name.name;
struct inode *new_inode = new_dentry->d_inode;
struct exfat_inode_info *new_ei = NULL;
if (new_path == NULL || strlen(new_path) == 0)
return -EINVAL;
if (ei->dir.dir == DIR_DELETED) {
exfat_err(sb, "abnormal access to deleted source dentry");
return -ENOENT;
}
if (new_inode) {
ret = -EIO;
new_ei = EXFAT_I(new_inode);
if (new_ei->dir.dir == DIR_DELETED) {
exfat_err(sb, "abnormal access to deleted target dentry");
goto out;
}
if (S_ISDIR(new_inode->i_mode)) {
struct exfat_chain new_clu;
new_clu.dir = new_ei->start_clu;
new_clu.size =
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode),
sbi);
new_clu.flags = new_ei->flags;
ret = exfat_check_dir_empty(sb, &new_clu);
if (ret)
goto out;
}
}
ret = exfat_resolve_path(new_parent_inode, new_path, &uni_name);
if (ret)
goto out;
exfat_set_volume_dirty(sb);
if (new_parent_inode == old_parent_inode)
ret = exfat_rename_file(new_parent_inode, &uni_name, ei);
else
ret = exfat_move_file(new_parent_inode, &uni_name, ei);
if (!ret && new_inode) {
struct exfat_entry_set_cache es;
ret = exfat_get_dentry_set_by_ei(&es, sb, new_ei);
if (ret) {
ret = -EIO;
goto del_out;
}
exfat_remove_entries(new_inode, &es, ES_IDX_FILE);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode));
if (ret)
goto del_out;
if (S_ISDIR(new_inode->i_mode) &&
new_ei->start_clu != EXFAT_EOF_CLUSTER) {
struct exfat_chain new_clu_to_free;
exfat_chain_set(&new_clu_to_free, new_ei->start_clu,
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode),
sbi), new_ei->flags);
if (exfat_free_cluster(new_inode, &new_clu_to_free)) {
ret = -EIO;
}
i_size_write(new_inode, 0);
new_ei->valid_size = 0;
new_ei->start_clu = EXFAT_EOF_CLUSTER;
new_ei->flags = ALLOC_NO_FAT_CHAIN;
}
del_out:
new_ei->dir.dir = DIR_DELETED;
}
out:
return ret;
}
static int exfat_rename(struct mnt_idmap *idmap,
struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
{
struct inode *old_inode, *new_inode;
struct super_block *sb = old_dir->i_sb;
loff_t i_pos;
int err;
loff_t size = i_size_read(new_dir);
if (flags & ~RENAME_NOREPLACE)
return -EINVAL;
mutex_lock(&EXFAT_SB(sb)->s_lock);
old_inode = old_dentry->d_inode;
new_inode = new_dentry->d_inode;
err = __exfat_rename(old_dir, EXFAT_I(old_inode), new_dir, new_dentry);
if (err)
goto unlock;
inode_inc_iversion(new_dir);
simple_rename_timestamp(old_dir, old_dentry, new_dir, new_dentry);
EXFAT_I(new_dir)->i_crtime = current_time(new_dir);
exfat_truncate_inode_atime(new_dir);
if (IS_DIRSYNC(new_dir) && size != i_size_read(new_dir))
exfat_sync_inode(new_dir);
else
mark_inode_dirty(new_dir);
i_pos = ((loff_t)EXFAT_I(old_inode)->dir.dir << 32) |
(EXFAT_I(old_inode)->entry & 0xffffffff);
exfat_unhash_inode(old_inode);
exfat_hash_inode(old_inode, i_pos);
if (IS_DIRSYNC(new_dir))
exfat_sync_inode(old_inode);
else
mark_inode_dirty(old_inode);
if (S_ISDIR(old_inode->i_mode) && old_dir != new_dir) {
drop_nlink(old_dir);
if (!new_inode)
inc_nlink(new_dir);
}
inode_inc_iversion(old_dir);
if (new_dir != old_dir)
mark_inode_dirty(old_dir);
if (new_inode) {
exfat_unhash_inode(new_inode);
if (new_inode->i_nlink) {
drop_nlink(new_inode);
if (S_ISDIR(new_inode->i_mode))
drop_nlink(new_inode);
} else {
exfat_warn(sb, "abnormal access to an inode dropped");
WARN_ON(new_inode->i_nlink == 0);
}
EXFAT_I(new_inode)->i_crtime = current_time(new_inode);
}
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return err;
}
const struct inode_operations exfat_dir_inode_operations = {
.create = exfat_create,
.lookup = exfat_lookup,
.unlink = exfat_unlink,
.mkdir = exfat_mkdir,
.rmdir = exfat_rmdir,
.rename = exfat_rename,
.setattr = exfat_setattr,
.getattr = exfat_getattr,
};