mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-14 06:24:53 +08:00
b74d24f7a7
Convert to struct mnt_idmap.
Last cycle we merged the necessary infrastructure in
256c8aed2b
("fs: introduce dedicated idmap type for mounts").
This is just the conversion to struct mnt_idmap.
Currently we still pass around the plain namespace that was attached to a
mount. This is in general pretty convenient but it makes it easy to
conflate namespaces that are relevant on the filesystem with namespaces
that are relevent on the mount level. Especially for non-vfs developers
without detailed knowledge in this area this can be a potential source for
bugs.
Once the conversion to struct mnt_idmap is done all helpers down to the
really low-level helpers will take a struct mnt_idmap argument instead of
two namespace arguments. This way it becomes impossible to conflate the two
eliminating the possibility of any bugs. All of the vfs and all filesystems
only operate on struct mnt_idmap.
Acked-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org>
401 lines
9.9 KiB
C
401 lines
9.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/cred.h>
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/blkdev.h>
|
|
|
|
#include "exfat_raw.h"
|
|
#include "exfat_fs.h"
|
|
|
|
static int exfat_cont_expand(struct inode *inode, loff_t size)
|
|
{
|
|
struct address_space *mapping = inode->i_mapping;
|
|
loff_t start = i_size_read(inode), count = size - i_size_read(inode);
|
|
int err, err2;
|
|
|
|
err = generic_cont_expand_simple(inode, size);
|
|
if (err)
|
|
return err;
|
|
|
|
inode->i_ctime = inode->i_mtime = current_time(inode);
|
|
mark_inode_dirty(inode);
|
|
|
|
if (!IS_SYNC(inode))
|
|
return 0;
|
|
|
|
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)
|
|
return err;
|
|
|
|
return filemap_fdatawait_range(mapping, start, start + count - 1);
|
|
}
|
|
|
|
static bool exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode)
|
|
{
|
|
mode_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 true;
|
|
}
|
|
|
|
/* use a default check */
|
|
return false;
|
|
}
|
|
|
|
static int exfat_sanitize_mode(const struct exfat_sb_info *sbi,
|
|
struct inode *inode, umode_t *mode_ptr)
|
|
{
|
|
mode_t i_mode, mask, perm;
|
|
|
|
i_mode = inode->i_mode;
|
|
|
|
mask = (S_ISREG(i_mode) || S_ISLNK(i_mode)) ?
|
|
sbi->options.fs_fmask : sbi->options.fs_dmask;
|
|
perm = *mode_ptr & ~(S_IFMT | mask);
|
|
|
|
/* Of the r and x bits, all (subject to umask) must be present.*/
|
|
if ((perm & 0555) != (i_mode & 0555))
|
|
return -EPERM;
|
|
|
|
if (exfat_mode_can_hold_ro(inode)) {
|
|
/*
|
|
* Of the w bits, either all (subject to umask) or none must
|
|
* be present.
|
|
*/
|
|
if ((perm & 0222) && ((perm & 0222) != (0222 & ~mask)))
|
|
return -EPERM;
|
|
} else {
|
|
/*
|
|
* If exfat_mode_can_hold_ro(inode) is false, can't change
|
|
* w bits.
|
|
*/
|
|
if ((perm & 0222) != (0222 & ~mask))
|
|
return -EPERM;
|
|
}
|
|
|
|
*mode_ptr &= S_IFMT | perm;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* resize the file length */
|
|
int __exfat_truncate(struct inode *inode)
|
|
{
|
|
unsigned int num_clusters_new, num_clusters_phys;
|
|
unsigned int last_clu = EXFAT_FREE_CLUSTER;
|
|
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);
|
|
|
|
/* check if the given file ID is opened */
|
|
if (ei->type != TYPE_FILE && ei->type != TYPE_DIR)
|
|
return -EPERM;
|
|
|
|
exfat_set_volume_dirty(sb);
|
|
|
|
num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi);
|
|
num_clusters_phys = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi);
|
|
|
|
exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags);
|
|
|
|
if (i_size_read(inode) > 0) {
|
|
/*
|
|
* Truncate FAT chain num_clusters after the first cluster
|
|
* num_clusters = min(new, phys);
|
|
*/
|
|
unsigned int num_clusters =
|
|
min(num_clusters_new, num_clusters_phys);
|
|
|
|
/*
|
|
* Follow FAT chain
|
|
* (defensive coding - works fine even with corrupted FAT table
|
|
*/
|
|
if (clu.flags == ALLOC_NO_FAT_CHAIN) {
|
|
clu.dir += num_clusters;
|
|
clu.size -= num_clusters;
|
|
} else {
|
|
while (num_clusters > 0) {
|
|
last_clu = clu.dir;
|
|
if (exfat_get_next_cluster(sb, &(clu.dir)))
|
|
return -EIO;
|
|
|
|
num_clusters--;
|
|
clu.size--;
|
|
}
|
|
}
|
|
} else {
|
|
ei->flags = ALLOC_NO_FAT_CHAIN;
|
|
ei->start_clu = EXFAT_EOF_CLUSTER;
|
|
}
|
|
|
|
if (ei->type == TYPE_FILE)
|
|
ei->attr |= ATTR_ARCHIVE;
|
|
|
|
/*
|
|
* update the directory entry
|
|
*
|
|
* If the directory entry is updated by mark_inode_dirty(), the
|
|
* directory entry will be written after a writeback cycle of
|
|
* updating the bitmap/FAT, which may result in clusters being
|
|
* freed but referenced by the directory entry in the event of a
|
|
* sudden power failure.
|
|
* __exfat_write_inode() is called for directory entry, bitmap
|
|
* and FAT to be written in a same writeback.
|
|
*/
|
|
if (__exfat_write_inode(inode, inode_needs_sync(inode)))
|
|
return -EIO;
|
|
|
|
/* cut off from the FAT chain */
|
|
if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER &&
|
|
last_clu != EXFAT_EOF_CLUSTER) {
|
|
if (exfat_ent_set(sb, last_clu, EXFAT_EOF_CLUSTER))
|
|
return -EIO;
|
|
}
|
|
|
|
/* invalidate cache and free the clusters */
|
|
/* clear exfat cache */
|
|
exfat_cache_inval_inode(inode);
|
|
|
|
/* hint information */
|
|
ei->hint_bmap.off = EXFAT_EOF_CLUSTER;
|
|
ei->hint_bmap.clu = EXFAT_EOF_CLUSTER;
|
|
|
|
/* hint_stat will be used if this is directory. */
|
|
ei->hint_stat.eidx = 0;
|
|
ei->hint_stat.clu = ei->start_clu;
|
|
ei->hint_femp.eidx = EXFAT_HINT_NONE;
|
|
|
|
/* free the clusters */
|
|
if (exfat_free_cluster(inode, &clu))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void exfat_truncate(struct inode *inode)
|
|
{
|
|
struct super_block *sb = inode->i_sb;
|
|
struct exfat_sb_info *sbi = EXFAT_SB(sb);
|
|
struct exfat_inode_info *ei = EXFAT_I(inode);
|
|
unsigned int blocksize = i_blocksize(inode);
|
|
loff_t aligned_size;
|
|
int err;
|
|
|
|
mutex_lock(&sbi->s_lock);
|
|
if (ei->start_clu == 0) {
|
|
/*
|
|
* Empty start_clu != ~0 (not allocated)
|
|
*/
|
|
exfat_fs_error(sb, "tried to truncate zeroed cluster.");
|
|
goto write_size;
|
|
}
|
|
|
|
err = __exfat_truncate(inode);
|
|
if (err)
|
|
goto write_size;
|
|
|
|
inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >>
|
|
inode->i_blkbits;
|
|
write_size:
|
|
aligned_size = i_size_read(inode);
|
|
if (aligned_size & (blocksize - 1)) {
|
|
aligned_size |= (blocksize - 1);
|
|
aligned_size++;
|
|
}
|
|
|
|
if (ei->i_size_ondisk > i_size_read(inode))
|
|
ei->i_size_ondisk = aligned_size;
|
|
|
|
if (ei->i_size_aligned > i_size_read(inode))
|
|
ei->i_size_aligned = aligned_size;
|
|
mutex_unlock(&sbi->s_lock);
|
|
}
|
|
|
|
int exfat_getattr(struct mnt_idmap *idmap, const struct path *path,
|
|
struct kstat *stat, unsigned int request_mask,
|
|
unsigned int query_flags)
|
|
{
|
|
struct inode *inode = d_backing_inode(path->dentry);
|
|
struct exfat_inode_info *ei = EXFAT_I(inode);
|
|
|
|
generic_fillattr(&nop_mnt_idmap, inode, stat);
|
|
exfat_truncate_atime(&stat->atime);
|
|
stat->result_mask |= STATX_BTIME;
|
|
stat->btime.tv_sec = ei->i_crtime.tv_sec;
|
|
stat->btime.tv_nsec = ei->i_crtime.tv_nsec;
|
|
stat->blksize = EXFAT_SB(inode->i_sb)->cluster_size;
|
|
return 0;
|
|
}
|
|
|
|
int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
|
|
struct iattr *attr)
|
|
{
|
|
struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb);
|
|
struct inode *inode = dentry->d_inode;
|
|
unsigned int ia_valid;
|
|
int error;
|
|
|
|
if ((attr->ia_valid & ATTR_SIZE) &&
|
|
attr->ia_size > i_size_read(inode)) {
|
|
error = exfat_cont_expand(inode, attr->ia_size);
|
|
if (error || attr->ia_valid == ATTR_SIZE)
|
|
return error;
|
|
attr->ia_valid &= ~ATTR_SIZE;
|
|
}
|
|
|
|
/* Check for setting the inode time. */
|
|
ia_valid = attr->ia_valid;
|
|
if ((ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) &&
|
|
exfat_allow_set_time(sbi, inode)) {
|
|
attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET |
|
|
ATTR_TIMES_SET);
|
|
}
|
|
|
|
error = setattr_prepare(&nop_mnt_idmap, dentry, attr);
|
|
attr->ia_valid = ia_valid;
|
|
if (error)
|
|
goto out;
|
|
|
|
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 & ~(S_IFREG | S_IFLNK | S_IFDIR | 0777)))) {
|
|
error = -EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* We don't return -EPERM here. Yes, strange, but this is too
|
|
* old behavior.
|
|
*/
|
|
if (attr->ia_valid & ATTR_MODE) {
|
|
if (exfat_sanitize_mode(sbi, inode, &attr->ia_mode) < 0)
|
|
attr->ia_valid &= ~ATTR_MODE;
|
|
}
|
|
|
|
if (attr->ia_valid & ATTR_SIZE)
|
|
inode->i_mtime = inode->i_ctime = current_time(inode);
|
|
|
|
setattr_copy(&nop_mnt_idmap, inode, attr);
|
|
exfat_truncate_atime(&inode->i_atime);
|
|
|
|
if (attr->ia_valid & ATTR_SIZE) {
|
|
error = exfat_block_truncate_page(inode, attr->ia_size);
|
|
if (error)
|
|
goto out;
|
|
|
|
down_write(&EXFAT_I(inode)->truncate_lock);
|
|
truncate_setsize(inode, attr->ia_size);
|
|
|
|
/*
|
|
* __exfat_write_inode() is called from exfat_truncate(), inode
|
|
* is already written by it, so mark_inode_dirty() is unneeded.
|
|
*/
|
|
exfat_truncate(inode);
|
|
up_write(&EXFAT_I(inode)->truncate_lock);
|
|
} else
|
|
mark_inode_dirty(inode);
|
|
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
|
|
{
|
|
struct fstrim_range range;
|
|
int ret = 0;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (!bdev_max_discard_sectors(inode->i_sb->s_bdev))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range)))
|
|
return -EFAULT;
|
|
|
|
range.minlen = max_t(unsigned int, range.minlen,
|
|
bdev_discard_granularity(inode->i_sb->s_bdev));
|
|
|
|
ret = exfat_trim_fs(inode, &range);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (copy_to_user((struct fstrim_range __user *)arg, &range, sizeof(range)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct inode *inode = file_inode(filp);
|
|
|
|
switch (cmd) {
|
|
case FITRIM:
|
|
return exfat_ioctl_fitrim(inode, arg);
|
|
default:
|
|
return -ENOTTY;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
return exfat_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
|
|
}
|
|
#endif
|
|
|
|
int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
|
|
{
|
|
struct inode *inode = filp->f_mapping->host;
|
|
int err;
|
|
|
|
err = __generic_file_fsync(filp, start, end, datasync);
|
|
if (err)
|
|
return err;
|
|
|
|
err = sync_blockdev(inode->i_sb->s_bdev);
|
|
if (err)
|
|
return err;
|
|
|
|
return blkdev_issue_flush(inode->i_sb->s_bdev);
|
|
}
|
|
|
|
const struct file_operations exfat_file_operations = {
|
|
.llseek = generic_file_llseek,
|
|
.read_iter = generic_file_read_iter,
|
|
.write_iter = generic_file_write_iter,
|
|
.unlocked_ioctl = exfat_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = exfat_compat_ioctl,
|
|
#endif
|
|
.mmap = generic_file_mmap,
|
|
.fsync = exfat_file_fsync,
|
|
.splice_read = generic_file_splice_read,
|
|
.splice_write = iter_file_splice_write,
|
|
};
|
|
|
|
const struct inode_operations exfat_file_inode_operations = {
|
|
.setattr = exfat_setattr,
|
|
.getattr = exfat_getattr,
|
|
};
|