mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 19:00:25 +00:00
![Takashi Iwai](/assets/img/avatar_default.png)
LTP has a test for oversized file path renames and it expects the return value to be ENAMETOOLONG. However, exfat returns EINVAL unexpectedly in some cases, hence LTP test fails. The further investigation indicated that the problem happens only when iocharset isn't set to utf8. The difference comes from that, in the case of utf8, exfat_utf8_to_utf16() returns the error -ENAMETOOLONG directly and it's treated as the final error code. Meanwhile, on other iocharsets, exfat_nls_to_ucs2() returns the max path size but it sets NLS_NAME_OVERLEN to lossy flag instead; the caller side checks only whether lossy flag is set or not, resulting in always -EINVAL unconditionally. This patch aligns the return code for both cases by checking the lossy flag bit and returning ENAMETOOLONG when NLS_NAME_OVERLEN bit is set. BugLink: https://bugzilla.suse.com/show_bug.cgi?id=1201725 Reviewed-by: Petr Vorel <pvorel@suse.cz> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
1380 lines
35 KiB
C
1380 lines
35 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
|
|
*/
|
|
|
|
#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;
|
|
}
|
|
|
|
/*
|
|
* If new entry was created in the parent, it could create the 8.3 alias (the
|
|
* shortname of logname). So, the parent may have the negative-dentry which
|
|
* matches the created 8.3 alias.
|
|
*
|
|
* If it happened, the negative dentry isn't actually negative anymore. So,
|
|
* drop it.
|
|
*/
|
|
static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags)
|
|
{
|
|
int ret;
|
|
|
|
if (flags & LOOKUP_RCU)
|
|
return -ECHILD;
|
|
|
|
/*
|
|
* This is not negative dentry. Always valid.
|
|
*
|
|
* Note, rename() to existing directory entry will have ->d_inode, and
|
|
* will use existing name which isn't specified name by user.
|
|
*
|
|
* We may be able to drop this positive dentry here. But dropping
|
|
* positive dentry isn't good idea. So it's unsupported like
|
|
* rename("filename", "FILENAME") for now.
|
|
*/
|
|
if (d_really_is_positive(dentry))
|
|
return 1;
|
|
|
|
/*
|
|
* Drop the negative dentry, in order to make sure to use the case
|
|
* sensitive name which is specified by user if this is for creation.
|
|
*/
|
|
if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))
|
|
return 0;
|
|
|
|
spin_lock(&dentry->d_lock);
|
|
ret = inode_eq_iversion(d_inode(dentry->d_parent),
|
|
exfat_d_version(dentry));
|
|
spin_unlock(&dentry->d_lock);
|
|
return ret;
|
|
}
|
|
|
|
/* returns the length of a struct qstr, ignoring trailing dots if necessary */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Compute the hash for the exfat name corresponding to the dentry. If the name
|
|
* is invalid, we leave the hash code unchanged so that the existing dentry can
|
|
* be used. The exfat fs routines will return ENOENT or EINVAL as appropriate.
|
|
*/
|
|
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;
|
|
|
|
/*
|
|
* exfat_toupper() works only for code points up to the U+FFFF.
|
|
*/
|
|
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,
|
|
};
|
|
|
|
/* used only in search empty_slot() */
|
|
#define CNT_UNUSED_NOHIT (-1)
|
|
#define CNT_UNUSED_HIT (-2)
|
|
/* search EMPTY CONTINUOUS "num_entries" entries */
|
|
static int exfat_search_empty_slot(struct super_block *sb,
|
|
struct exfat_hint_femp *hint_femp, struct exfat_chain *p_dir,
|
|
int num_entries)
|
|
{
|
|
int i, dentry, num_empty = 0;
|
|
int dentries_per_clu;
|
|
unsigned int type;
|
|
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 (hint_femp->eidx != EXFAT_HINT_NONE) {
|
|
dentry = hint_femp->eidx;
|
|
if (num_entries <= hint_femp->count) {
|
|
hint_femp->eidx = EXFAT_HINT_NONE;
|
|
return dentry;
|
|
}
|
|
|
|
exfat_chain_dup(&clu, &hint_femp->cur);
|
|
} else {
|
|
exfat_chain_dup(&clu, p_dir);
|
|
dentry = 0;
|
|
}
|
|
|
|
while (clu.dir != EXFAT_EOF_CLUSTER) {
|
|
i = dentry & (dentries_per_clu - 1);
|
|
|
|
for (; i < dentries_per_clu; i++, dentry++) {
|
|
ep = exfat_get_dentry(sb, &clu, i, &bh);
|
|
if (!ep)
|
|
return -EIO;
|
|
type = exfat_get_entry_type(ep);
|
|
brelse(bh);
|
|
|
|
if (type == TYPE_UNUSED || type == TYPE_DELETED) {
|
|
num_empty++;
|
|
if (hint_femp->eidx == EXFAT_HINT_NONE) {
|
|
hint_femp->eidx = dentry;
|
|
hint_femp->count = CNT_UNUSED_NOHIT;
|
|
exfat_chain_set(&hint_femp->cur,
|
|
clu.dir, clu.size, clu.flags);
|
|
}
|
|
|
|
if (type == TYPE_UNUSED &&
|
|
hint_femp->count != CNT_UNUSED_HIT)
|
|
hint_femp->count = CNT_UNUSED_HIT;
|
|
} else {
|
|
if (hint_femp->eidx != EXFAT_HINT_NONE &&
|
|
hint_femp->count == CNT_UNUSED_HIT) {
|
|
/* unused empty group means
|
|
* an empty group which includes
|
|
* unused dentry
|
|
*/
|
|
exfat_fs_error(sb,
|
|
"found bogus dentry(%d) beyond unused empty group(%d) (start_clu : %u, cur_clu : %u)",
|
|
dentry, hint_femp->eidx,
|
|
p_dir->dir, clu.dir);
|
|
return -EIO;
|
|
}
|
|
|
|
num_empty = 0;
|
|
hint_femp->eidx = EXFAT_HINT_NONE;
|
|
}
|
|
|
|
if (num_empty >= num_entries) {
|
|
/* found and invalidate hint_femp */
|
|
hint_femp->eidx = EXFAT_HINT_NONE;
|
|
return (dentry - (num_entries - 1));
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return -ENOSPC;
|
|
}
|
|
|
|
static int exfat_check_max_dentries(struct inode *inode)
|
|
{
|
|
if (EXFAT_B_TO_DEN(i_size_read(inode)) >= MAX_EXFAT_DENTRIES) {
|
|
/*
|
|
* exFAT spec allows a dir to grow up to 8388608(256MB)
|
|
* dentries
|
|
*/
|
|
return -ENOSPC;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* find empty directory entry.
|
|
* if there isn't any empty slot, expand cluster chain.
|
|
*/
|
|
static int exfat_find_empty_entry(struct inode *inode,
|
|
struct exfat_chain *p_dir, int num_entries)
|
|
{
|
|
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;
|
|
}
|
|
|
|
while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir,
|
|
num_entries)) < 0) {
|
|
if (dentry == -EIO)
|
|
break;
|
|
|
|
if (exfat_check_max_dentries(inode))
|
|
return -ENOSPC;
|
|
|
|
/* we trust p_dir->size regardless of FAT type */
|
|
if (exfat_find_last_cluster(sb, p_dir, &last_clu))
|
|
return -EIO;
|
|
|
|
/*
|
|
* Allocate new cluster to this directory
|
|
*/
|
|
exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags);
|
|
|
|
/* allocate a cluster */
|
|
ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode));
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (exfat_zeroed_cluster(inode, clu.dir))
|
|
return -EIO;
|
|
|
|
/* append to the FAT chain */
|
|
if (clu.flags != p_dir->flags) {
|
|
/* no-fat-chain bit is disabled,
|
|
* so fat-chain should be synced with alloc-bitmap
|
|
*/
|
|
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.eidx == EXFAT_HINT_NONE) {
|
|
/* the special case that new dentry
|
|
* should be allocated from the start of new cluster
|
|
*/
|
|
hint_femp.eidx = EXFAT_B_TO_DEN_IDX(p_dir->size, sbi);
|
|
hint_femp.count = sbi->dentries_per_clu;
|
|
|
|
exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags);
|
|
}
|
|
hint_femp.cur.size++;
|
|
p_dir->size++;
|
|
size = EXFAT_CLU_TO_B(p_dir->size, sbi);
|
|
|
|
/* directory inode should be updated in here */
|
|
i_size_write(inode, size);
|
|
ei->i_size_ondisk += sbi->cluster_size;
|
|
ei->i_size_aligned += sbi->cluster_size;
|
|
ei->flags = p_dir->flags;
|
|
inode->i_blocks += 1 << sbi->sect_per_clus_bits;
|
|
}
|
|
|
|
return dentry;
|
|
}
|
|
|
|
/*
|
|
* Name Resolution Functions :
|
|
* Zero if it was successful; otherwise nonzero.
|
|
*/
|
|
static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
|
|
struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
|
|
int lookup)
|
|
{
|
|
int namelen;
|
|
int lossy = NLS_NAME_NO_LOSSY;
|
|
struct super_block *sb = inode->i_sb;
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
struct exfat_inode_info *ei = EXFAT_I(inode);
|
|
int pathlen = strlen(path);
|
|
|
|
/*
|
|
* get the length of the pathname excluding
|
|
* trailing periods, if any.
|
|
*/
|
|
namelen = exfat_striptail_len(pathlen, path, false);
|
|
if (EXFAT_SB(sb)->options.keep_last_dots) {
|
|
/*
|
|
* Do not allow the creation of files with names
|
|
* ending with period(s).
|
|
*/
|
|
if (!lookup && (namelen < pathlen))
|
|
return -EINVAL;
|
|
namelen = pathlen;
|
|
}
|
|
if (!namelen)
|
|
return -ENOENT;
|
|
if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
|
|
return -ENAMETOOLONG;
|
|
|
|
/*
|
|
* strip all leading spaces :
|
|
* "MS windows 7" supports leading spaces.
|
|
* So we should skip this preprocessing for compatibility.
|
|
*/
|
|
|
|
/* file name conversion :
|
|
* If lookup case, we allow bad-name for compatibility.
|
|
*/
|
|
namelen = exfat_nls_to_utf16(sb, path, namelen, p_uniname,
|
|
&lossy);
|
|
if (namelen < 0)
|
|
return namelen; /* return error value */
|
|
|
|
if ((lossy && !lookup) || !namelen)
|
|
return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL;
|
|
|
|
exfat_chain_set(p_dir, ei->start_clu,
|
|
EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int exfat_resolve_path(struct inode *inode,
|
|
const unsigned char *path, struct exfat_chain *dir,
|
|
struct exfat_uni_name *uni)
|
|
{
|
|
return __exfat_resolve_path(inode, path, dir, uni, 0);
|
|
}
|
|
|
|
static inline int exfat_resolve_path_for_lookup(struct inode *inode,
|
|
const unsigned char *path, struct exfat_chain *dir,
|
|
struct exfat_uni_name *uni)
|
|
{
|
|
return __exfat_resolve_path(inode, path, dir, 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,
|
|
struct exfat_chain *p_dir, 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;
|
|
int clu_size = 0;
|
|
unsigned int start_clu = EXFAT_FREE_CLUSTER;
|
|
|
|
ret = exfat_resolve_path(inode, path, p_dir, &uniname);
|
|
if (ret)
|
|
goto out;
|
|
|
|
num_entries = exfat_calc_num_entries(&uniname);
|
|
if (num_entries < 0) {
|
|
ret = num_entries;
|
|
goto out;
|
|
}
|
|
|
|
/* exfat_find_empty_entry must be called before alloc_cluster() */
|
|
dentry = exfat_find_empty_entry(inode, p_dir, num_entries);
|
|
if (dentry < 0) {
|
|
ret = dentry; /* -EIO or -ENOSPC */
|
|
goto out;
|
|
}
|
|
|
|
if (type == TYPE_DIR) {
|
|
ret = exfat_alloc_new_dir(inode, &clu);
|
|
if (ret)
|
|
goto out;
|
|
start_clu = clu.dir;
|
|
clu_size = sbi->cluster_size;
|
|
}
|
|
|
|
/* update the directory entry */
|
|
/* fill the dos name directory entry information of the created file.
|
|
* the first cluster is not determined yet. (0)
|
|
*/
|
|
ret = exfat_init_dir_entry(inode, p_dir, dentry, type,
|
|
start_clu, clu_size);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname);
|
|
if (ret)
|
|
goto out;
|
|
|
|
info->dir = *p_dir;
|
|
info->entry = dentry;
|
|
info->flags = ALLOC_NO_FAT_CHAIN;
|
|
info->type = type;
|
|
|
|
if (type == TYPE_FILE) {
|
|
info->attr = ATTR_ARCHIVE;
|
|
info->start_clu = EXFAT_EOF_CLUSTER;
|
|
info->size = 0;
|
|
info->num_subdirs = 0;
|
|
} else {
|
|
info->attr = ATTR_SUBDIR;
|
|
info->start_clu = start_clu;
|
|
info->size = clu_size;
|
|
info->num_subdirs = EXFAT_MIN_SUBDIR;
|
|
}
|
|
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 user_namespace *mnt_userns, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, bool excl)
|
|
{
|
|
struct super_block *sb = dir->i_sb;
|
|
struct inode *inode;
|
|
struct exfat_chain cdir;
|
|
struct exfat_dir_entry info;
|
|
loff_t i_pos;
|
|
int err;
|
|
|
|
mutex_lock(&EXFAT_SB(sb)->s_lock);
|
|
exfat_set_volume_dirty(sb);
|
|
err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_FILE,
|
|
&info);
|
|
if (err)
|
|
goto unlock;
|
|
|
|
inode_inc_iversion(dir);
|
|
dir->i_ctime = dir->i_mtime = current_time(dir);
|
|
if (IS_DIRSYNC(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);
|
|
inode->i_mtime = inode->i_atime = inode->i_ctime =
|
|
EXFAT_I(inode)->i_crtime = current_time(inode);
|
|
exfat_truncate_atime(&inode->i_atime);
|
|
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
|
|
|
|
d_instantiate(dentry, inode);
|
|
unlock:
|
|
mutex_unlock(&EXFAT_SB(sb)->s_lock);
|
|
return err;
|
|
}
|
|
|
|
/* lookup a file */
|
|
static int exfat_find(struct inode *dir, struct qstr *qname,
|
|
struct exfat_dir_entry *info)
|
|
{
|
|
int ret, dentry, num_entries, 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;
|
|
/* for optimized dir & entry to prevent long traverse of cluster chain */
|
|
struct exfat_hint hint_opt;
|
|
|
|
if (qname->len == 0)
|
|
return -ENOENT;
|
|
|
|
/* check the validity of directory name in the given pathname */
|
|
ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
num_entries = exfat_calc_num_entries(&uni_name);
|
|
if (num_entries < 0)
|
|
return num_entries;
|
|
|
|
/* check the validation of hint_stat and initialize it if required */
|
|
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;
|
|
}
|
|
|
|
/* search the file name for directories */
|
|
dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name,
|
|
num_entries, TYPE_ALL, &hint_opt);
|
|
|
|
if (dentry < 0)
|
|
return dentry; /* -error value */
|
|
|
|
info->dir = cdir;
|
|
info->entry = dentry;
|
|
info->num_subdirs = 0;
|
|
|
|
/* adjust cdir to the optimized value */
|
|
cdir.dir = hint_opt.clu;
|
|
if (cdir.flags & ALLOC_NO_FAT_CHAIN)
|
|
cdir.size -= dentry / sbi->dentries_per_clu;
|
|
dentry = hint_opt.eidx;
|
|
es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES);
|
|
if (!es)
|
|
return -EIO;
|
|
ep = exfat_get_dentry_cached(es, 0);
|
|
ep2 = exfat_get_dentry_cached(es, 1);
|
|
|
|
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);
|
|
if ((info->type == TYPE_FILE) && (info->size == 0)) {
|
|
info->flags = ALLOC_NO_FAT_CHAIN;
|
|
info->start_clu = EXFAT_EOF_CLUSTER;
|
|
} else {
|
|
info->flags = ep2->dentry.stream.flags;
|
|
info->start_clu =
|
|
le32_to_cpu(ep2->dentry.stream.start_clu);
|
|
}
|
|
|
|
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_free_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);
|
|
|
|
/*
|
|
* Checking "alias->d_parent == dentry->d_parent" to make sure
|
|
* FS is not corrupted (especially double linked dir).
|
|
*/
|
|
if (alias && alias->d_parent == dentry->d_parent &&
|
|
!exfat_d_anon_disconn(alias)) {
|
|
|
|
/*
|
|
* Unhashed alias is able to exist because of revalidate()
|
|
* called by lookup_fast. You can easily make this status
|
|
* by calling create and lookup concurrently
|
|
* In such case, we reuse an alias instead of new dentry
|
|
*/
|
|
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)) {
|
|
/*
|
|
* This inode has non anonymous-DCACHE_DISCONNECTED
|
|
* dentry. This means, the user did ->lookup() by an
|
|
* another name (longname vs 8.3 alias of it) in past.
|
|
*
|
|
* Switch to new one for reason of locality if possible.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/* remove an entry, BUT don't truncate */
|
|
static int exfat_unlink(struct inode *dir, struct dentry *dentry)
|
|
{
|
|
struct exfat_chain cdir;
|
|
struct exfat_dentry *ep;
|
|
struct super_block *sb = dir->i_sb;
|
|
struct inode *inode = dentry->d_inode;
|
|
struct exfat_inode_info *ei = EXFAT_I(inode);
|
|
struct buffer_head *bh;
|
|
int num_entries, entry, err = 0;
|
|
|
|
mutex_lock(&EXFAT_SB(sb)->s_lock);
|
|
exfat_chain_dup(&cdir, &ei->dir);
|
|
entry = ei->entry;
|
|
if (ei->dir.dir == DIR_DELETED) {
|
|
exfat_err(sb, "abnormal access to deleted dentry");
|
|
err = -ENOENT;
|
|
goto unlock;
|
|
}
|
|
|
|
ep = exfat_get_dentry(sb, &cdir, entry, &bh);
|
|
if (!ep) {
|
|
err = -EIO;
|
|
goto unlock;
|
|
}
|
|
num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep);
|
|
if (num_entries < 0) {
|
|
err = -EIO;
|
|
brelse(bh);
|
|
goto unlock;
|
|
}
|
|
num_entries++;
|
|
brelse(bh);
|
|
|
|
exfat_set_volume_dirty(sb);
|
|
/* update the directory entry */
|
|
if (exfat_remove_entries(dir, &cdir, entry, 0, num_entries)) {
|
|
err = -EIO;
|
|
goto unlock;
|
|
}
|
|
|
|
/* This doesn't modify ei */
|
|
ei->dir.dir = DIR_DELETED;
|
|
|
|
inode_inc_iversion(dir);
|
|
dir->i_mtime = dir->i_atime = current_time(dir);
|
|
exfat_truncate_atime(&dir->i_atime);
|
|
if (IS_DIRSYNC(dir))
|
|
exfat_sync_inode(dir);
|
|
else
|
|
mark_inode_dirty(dir);
|
|
|
|
clear_nlink(inode);
|
|
inode->i_mtime = inode->i_atime = current_time(inode);
|
|
exfat_truncate_atime(&inode->i_atime);
|
|
exfat_unhash_inode(inode);
|
|
exfat_d_version_set(dentry, inode_query_iversion(dir));
|
|
unlock:
|
|
mutex_unlock(&EXFAT_SB(sb)->s_lock);
|
|
return err;
|
|
}
|
|
|
|
static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode)
|
|
{
|
|
struct super_block *sb = dir->i_sb;
|
|
struct inode *inode;
|
|
struct exfat_dir_entry info;
|
|
struct exfat_chain cdir;
|
|
loff_t i_pos;
|
|
int err;
|
|
|
|
mutex_lock(&EXFAT_SB(sb)->s_lock);
|
|
exfat_set_volume_dirty(sb);
|
|
err = exfat_add_entry(dir, dentry->d_name.name, &cdir, TYPE_DIR,
|
|
&info);
|
|
if (err)
|
|
goto unlock;
|
|
|
|
inode_inc_iversion(dir);
|
|
dir->i_ctime = dir->i_mtime = current_time(dir);
|
|
if (IS_DIRSYNC(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);
|
|
inode->i_mtime = inode->i_atime = inode->i_ctime =
|
|
EXFAT_I(inode)->i_crtime = current_time(inode);
|
|
exfat_truncate_atime(&inode->i_atime);
|
|
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
|
|
|
|
d_instantiate(dentry, inode);
|
|
|
|
unlock:
|
|
mutex_unlock(&EXFAT_SB(sb)->s_lock);
|
|
return err;
|
|
}
|
|
|
|
static int exfat_check_dir_empty(struct super_block *sb,
|
|
struct exfat_chain *p_dir)
|
|
{
|
|
int i, dentries_per_clu;
|
|
unsigned int type;
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
|
|
{
|
|
struct inode *inode = dentry->d_inode;
|
|
struct exfat_dentry *ep;
|
|
struct exfat_chain cdir, 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 buffer_head *bh;
|
|
int num_entries, entry, err;
|
|
|
|
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
|
|
|
|
exfat_chain_dup(&cdir, &ei->dir);
|
|
entry = ei->entry;
|
|
|
|
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;
|
|
}
|
|
|
|
ep = exfat_get_dentry(sb, &cdir, entry, &bh);
|
|
if (!ep) {
|
|
err = -EIO;
|
|
goto unlock;
|
|
}
|
|
|
|
num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep);
|
|
if (num_entries < 0) {
|
|
err = -EIO;
|
|
brelse(bh);
|
|
goto unlock;
|
|
}
|
|
num_entries++;
|
|
brelse(bh);
|
|
|
|
exfat_set_volume_dirty(sb);
|
|
err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries);
|
|
if (err) {
|
|
exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err);
|
|
goto unlock;
|
|
}
|
|
ei->dir.dir = DIR_DELETED;
|
|
|
|
inode_inc_iversion(dir);
|
|
dir->i_mtime = dir->i_atime = current_time(dir);
|
|
exfat_truncate_atime(&dir->i_atime);
|
|
if (IS_DIRSYNC(dir))
|
|
exfat_sync_inode(dir);
|
|
else
|
|
mark_inode_dirty(dir);
|
|
drop_nlink(dir);
|
|
|
|
clear_nlink(inode);
|
|
inode->i_mtime = inode->i_atime = current_time(inode);
|
|
exfat_truncate_atime(&inode->i_atime);
|
|
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 *inode, struct exfat_chain *p_dir,
|
|
int oldentry, struct exfat_uni_name *p_uniname,
|
|
struct exfat_inode_info *ei)
|
|
{
|
|
int ret, num_old_entries, num_new_entries;
|
|
struct exfat_dentry *epold, *epnew;
|
|
struct super_block *sb = inode->i_sb;
|
|
struct buffer_head *new_bh, *old_bh;
|
|
int sync = IS_DIRSYNC(inode);
|
|
|
|
epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh);
|
|
if (!epold)
|
|
return -EIO;
|
|
|
|
num_old_entries = exfat_count_ext_entries(sb, p_dir, oldentry, epold);
|
|
if (num_old_entries < 0)
|
|
return -EIO;
|
|
num_old_entries++;
|
|
|
|
num_new_entries = exfat_calc_num_entries(p_uniname);
|
|
if (num_new_entries < 0)
|
|
return num_new_entries;
|
|
|
|
if (num_old_entries < num_new_entries) {
|
|
int newentry;
|
|
|
|
newentry =
|
|
exfat_find_empty_entry(inode, p_dir, num_new_entries);
|
|
if (newentry < 0)
|
|
return newentry; /* -EIO or -ENOSPC */
|
|
|
|
epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh);
|
|
if (!epnew)
|
|
return -EIO;
|
|
|
|
*epnew = *epold;
|
|
if (exfat_get_entry_type(epnew) == TYPE_FILE) {
|
|
epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE);
|
|
ei->attr |= ATTR_ARCHIVE;
|
|
}
|
|
exfat_update_bh(new_bh, sync);
|
|
brelse(old_bh);
|
|
brelse(new_bh);
|
|
|
|
epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh);
|
|
if (!epold)
|
|
return -EIO;
|
|
epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh);
|
|
if (!epnew) {
|
|
brelse(old_bh);
|
|
return -EIO;
|
|
}
|
|
|
|
*epnew = *epold;
|
|
exfat_update_bh(new_bh, sync);
|
|
brelse(old_bh);
|
|
brelse(new_bh);
|
|
|
|
ret = exfat_init_ext_entry(inode, p_dir, newentry,
|
|
num_new_entries, p_uniname);
|
|
if (ret)
|
|
return ret;
|
|
|
|
exfat_remove_entries(inode, p_dir, oldentry, 0,
|
|
num_old_entries);
|
|
ei->dir = *p_dir;
|
|
ei->entry = newentry;
|
|
} else {
|
|
if (exfat_get_entry_type(epold) == TYPE_FILE) {
|
|
epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE);
|
|
ei->attr |= ATTR_ARCHIVE;
|
|
}
|
|
exfat_update_bh(old_bh, sync);
|
|
brelse(old_bh);
|
|
ret = exfat_init_ext_entry(inode, p_dir, oldentry,
|
|
num_new_entries, p_uniname);
|
|
if (ret)
|
|
return ret;
|
|
|
|
exfat_remove_entries(inode, p_dir, oldentry, num_new_entries,
|
|
num_old_entries);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
|
|
int oldentry, struct exfat_chain *p_newdir,
|
|
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
|
|
{
|
|
int ret, newentry, num_new_entries, num_old_entries;
|
|
struct exfat_dentry *epmov, *epnew;
|
|
struct super_block *sb = inode->i_sb;
|
|
struct buffer_head *mov_bh, *new_bh;
|
|
|
|
epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh);
|
|
if (!epmov)
|
|
return -EIO;
|
|
|
|
num_old_entries = exfat_count_ext_entries(sb, p_olddir, oldentry,
|
|
epmov);
|
|
if (num_old_entries < 0)
|
|
return -EIO;
|
|
num_old_entries++;
|
|
|
|
num_new_entries = exfat_calc_num_entries(p_uniname);
|
|
if (num_new_entries < 0)
|
|
return num_new_entries;
|
|
|
|
newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries);
|
|
if (newentry < 0)
|
|
return newentry; /* -EIO or -ENOSPC */
|
|
|
|
epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh);
|
|
if (!epnew)
|
|
return -EIO;
|
|
|
|
*epnew = *epmov;
|
|
if (exfat_get_entry_type(epnew) == TYPE_FILE) {
|
|
epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE);
|
|
ei->attr |= ATTR_ARCHIVE;
|
|
}
|
|
exfat_update_bh(new_bh, IS_DIRSYNC(inode));
|
|
brelse(mov_bh);
|
|
brelse(new_bh);
|
|
|
|
epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh);
|
|
if (!epmov)
|
|
return -EIO;
|
|
epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh);
|
|
if (!epnew) {
|
|
brelse(mov_bh);
|
|
return -EIO;
|
|
}
|
|
|
|
*epnew = *epmov;
|
|
exfat_update_bh(new_bh, IS_DIRSYNC(inode));
|
|
brelse(mov_bh);
|
|
brelse(new_bh);
|
|
|
|
ret = exfat_init_ext_entry(inode, p_newdir, newentry, num_new_entries,
|
|
p_uniname);
|
|
if (ret)
|
|
return ret;
|
|
|
|
exfat_remove_entries(inode, p_olddir, oldentry, 0, num_old_entries);
|
|
|
|
exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
|
|
p_newdir->flags);
|
|
|
|
ei->entry = newentry;
|
|
return 0;
|
|
}
|
|
|
|
/* rename or move a old file into a new file */
|
|
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;
|
|
int dentry;
|
|
struct exfat_chain olddir, newdir;
|
|
struct exfat_chain *p_dir = NULL;
|
|
struct exfat_uni_name uni_name;
|
|
struct exfat_dentry *ep;
|
|
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;
|
|
int num_entries;
|
|
struct exfat_inode_info *new_ei = NULL;
|
|
unsigned int new_entry_type = TYPE_UNUSED;
|
|
int new_entry = 0;
|
|
struct buffer_head *old_bh, *new_bh = NULL;
|
|
|
|
/* check the validity of pointer parameters */
|
|
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;
|
|
}
|
|
|
|
exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu,
|
|
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi),
|
|
EXFAT_I(old_parent_inode)->flags);
|
|
dentry = ei->entry;
|
|
|
|
ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh);
|
|
if (!ep) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
brelse(old_bh);
|
|
|
|
/* check whether new dir is existing directory and empty */
|
|
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;
|
|
}
|
|
|
|
p_dir = &(new_ei->dir);
|
|
new_entry = new_ei->entry;
|
|
ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
|
|
if (!ep)
|
|
goto out;
|
|
|
|
new_entry_type = exfat_get_entry_type(ep);
|
|
brelse(new_bh);
|
|
|
|
/* if new_inode exists, update ei */
|
|
if (new_entry_type == TYPE_DIR) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* check the validity of directory name in the given new pathname */
|
|
ret = exfat_resolve_path(new_parent_inode, new_path, &newdir,
|
|
&uni_name);
|
|
if (ret)
|
|
goto out;
|
|
|
|
exfat_set_volume_dirty(sb);
|
|
|
|
if (olddir.dir == newdir.dir)
|
|
ret = exfat_rename_file(new_parent_inode, &olddir, dentry,
|
|
&uni_name, ei);
|
|
else
|
|
ret = exfat_move_file(new_parent_inode, &olddir, dentry,
|
|
&newdir, &uni_name, ei);
|
|
|
|
if (!ret && new_inode) {
|
|
/* delete entries of new_dir */
|
|
ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
|
|
if (!ep) {
|
|
ret = -EIO;
|
|
goto del_out;
|
|
}
|
|
|
|
num_entries = exfat_count_ext_entries(sb, p_dir, new_entry, ep);
|
|
if (num_entries < 0) {
|
|
ret = -EIO;
|
|
goto del_out;
|
|
}
|
|
brelse(new_bh);
|
|
|
|
if (exfat_remove_entries(new_inode, p_dir, new_entry, 0,
|
|
num_entries + 1)) {
|
|
ret = -EIO;
|
|
goto del_out;
|
|
}
|
|
|
|
/* Free the clusters if new_inode is a dir(as if exfat_rmdir) */
|
|
if (new_entry_type == TYPE_DIR) {
|
|
/* new_ei, new_clu_to_free */
|
|
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)) {
|
|
/* just set I/O error only */
|
|
ret = -EIO;
|
|
}
|
|
|
|
i_size_write(new_inode, 0);
|
|
new_ei->start_clu = EXFAT_EOF_CLUSTER;
|
|
new_ei->flags = ALLOC_NO_FAT_CHAIN;
|
|
}
|
|
del_out:
|
|
/* Update new_inode ei
|
|
* Prevent syncing removed new_inode
|
|
* (new_ei is already initialized above code ("if (new_inode)")
|
|
*/
|
|
new_ei->dir.dir = DIR_DELETED;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int exfat_rename(struct user_namespace *mnt_userns,
|
|
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;
|
|
|
|
/*
|
|
* The VFS already checks for existence, so for local filesystems
|
|
* the RENAME_NOREPLACE implementation is equivalent to plain rename.
|
|
* Don't support any other flags
|
|
*/
|
|
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);
|
|
new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime =
|
|
EXFAT_I(new_dir)->i_crtime = current_time(new_dir);
|
|
exfat_truncate_atime(&new_dir->i_atime);
|
|
if (IS_DIRSYNC(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);
|
|
old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir);
|
|
if (IS_DIRSYNC(old_dir))
|
|
exfat_sync_inode(old_dir);
|
|
else
|
|
mark_inode_dirty(old_dir);
|
|
|
|
if (new_inode) {
|
|
exfat_unhash_inode(new_inode);
|
|
|
|
/* skip drop_nlink if new_inode already has been dropped */
|
|
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);
|
|
}
|
|
new_inode->i_ctime = 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,
|
|
};
|