summaryrefslogtreecommitdiff
path: root/fs/exfat/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/exfat/file.c')
-rw-r--r--fs/exfat/file.c171
1 files changed, 148 insertions, 23 deletions
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 807349d8ea05..354bdcfe4abc 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -12,6 +12,8 @@
#include <linux/security.h>
#include <linux/msdos_fs.h>
#include <linux/writeback.h>
+#include <linux/filelock.h>
+#include <linux/falloc.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
@@ -25,11 +27,14 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_chain clu;
+ truncate_pagecache(inode, i_size_read(inode));
+
ret = inode_newsize_ok(inode, size);
if (ret)
return ret;
num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);
+ /* integer overflow is already checked in inode_newsize_ok(). */
new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi);
if (new_num_clusters == num_clusters)
@@ -87,6 +92,45 @@ free_clu:
return -EIO;
}
+/*
+ * Preallocate space for a file. This implements exfat's fallocate file
+ * operation, which gets called from sys_fallocate system call. User space
+ * requests len bytes at offset. In contrary to fat, we only support
+ * FALLOC_FL_ALLOCATE_RANGE because by leaving the valid data length(VDL)
+ * field, it is unnecessary to zero out the newly allocated clusters.
+ */
+static long exfat_fallocate(struct file *file, int mode,
+ loff_t offset, loff_t len)
+{
+ struct inode *inode = file->f_mapping->host;
+ loff_t newsize = offset + len;
+ int err = 0;
+
+ /* No support for other modes */
+ if (mode != FALLOC_FL_ALLOCATE_RANGE)
+ return -EOPNOTSUPP;
+
+ /* No support for dir */
+ if (!S_ISREG(inode->i_mode))
+ return -EOPNOTSUPP;
+
+ if (unlikely(exfat_forced_shutdown(inode->i_sb)))
+ return -EIO;
+
+ inode_lock(inode);
+
+ if (newsize <= i_size_read(inode))
+ goto error;
+
+ /* This is just an expanding truncate */
+ err = exfat_cont_expand(inode, newsize);
+
+error:
+ inode_unlock(inode);
+
+ return err;
+}
+
static bool exfat_allow_set_time(struct mnt_idmap *idmap,
struct exfat_sb_info *sbi, struct inode *inode)
{
@@ -486,6 +530,55 @@ static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
return exfat_force_shutdown(sb, flags);
}
+static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
+{
+ int ret;
+ char label[FSLABEL_MAX] = {0};
+ struct exfat_uni_name uniname;
+
+ ret = exfat_read_volume_label(sb, &uniname);
+ if (ret < 0)
+ return ret;
+
+ ret = exfat_utf16_to_nls(sb, &uniname, label, uniname.name_len);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user((char __user *)arg, label, ret + 1))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int exfat_ioctl_set_volume_label(struct super_block *sb,
+ unsigned long arg)
+{
+ int ret = 0, lossy, label_len;
+ char label[FSLABEL_MAX] = {0};
+ struct exfat_uni_name uniname;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(label, (char __user *)arg, FSLABEL_MAX))
+ return -EFAULT;
+
+ memset(&uniname, 0, sizeof(uniname));
+ label_len = strnlen(label, FSLABEL_MAX - 1);
+ if (label[0]) {
+ ret = exfat_nls_to_utf16(sb, label, label_len,
+ &uniname, &lossy);
+ if (ret < 0)
+ return ret;
+ else if (lossy & NLS_NAME_LOSSY)
+ return -EINVAL;
+ }
+
+ uniname.name_len = ret;
+
+ return exfat_write_volume_label(sb, &uniname);
+}
+
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
@@ -500,6 +593,10 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return exfat_ioctl_shutdown(inode->i_sb, arg);
case FITRIM:
return exfat_ioctl_fitrim(inode, arg);
+ case FS_IOC_GETFSLABEL:
+ return exfat_ioctl_get_volume_label(inode->i_sb, arg);
+ case FS_IOC_SETFSLABEL:
+ return exfat_ioctl_set_volume_label(inode->i_sb, arg);
default:
return -ENOTTY;
}
@@ -521,7 +618,7 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
- err = __generic_file_fsync(filp, start, end, datasync);
+ err = simple_fsync_noflush(filp, start, end, datasync);
if (err)
return err;
@@ -532,11 +629,10 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
return blkdev_issue_flush(inode->i_sb->s_bdev);
}
-static int exfat_extend_valid_size(struct file *file, loff_t new_valid_size)
+static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
{
int err;
loff_t pos;
- struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
struct address_space *mapping = inode->i_mapping;
const struct address_space_operations *ops = mapping->a_ops;
@@ -551,14 +647,14 @@ static int exfat_extend_valid_size(struct file *file, loff_t new_valid_size)
if (pos + len > new_valid_size)
len = new_valid_size - pos;
- err = ops->write_begin(file, mapping, pos, len, &folio, NULL);
+ err = ops->write_begin(NULL, mapping, pos, len, &folio, NULL);
if (err)
goto out;
off = offset_in_folio(folio, pos);
folio_zero_new_buffers(folio, off, off + len);
- err = ops->write_end(file, mapping, pos, len, len, folio, NULL);
+ err = ops->write_end(NULL, mapping, pos, len, len, folio, NULL);
if (err < 0)
goto out;
pos += len;
@@ -582,8 +678,14 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
loff_t pos = iocb->ki_pos;
loff_t valid_size;
+ if (unlikely(exfat_forced_shutdown(inode->i_sb)))
+ return -EIO;
+
inode_lock(inode);
+ if (pos > i_size_read(inode))
+ truncate_pagecache(inode, i_size_read(inode));
+
valid_size = ei->valid_size;
ret = generic_write_checks(iocb, iter);
@@ -601,7 +703,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
}
if (pos > valid_size) {
- ret = exfat_extend_valid_size(file, pos);
+ ret = exfat_extend_valid_size(inode, pos);
if (ret < 0 && ret != -ENOSPC) {
exfat_err(inode->i_sb,
"write: fail to zero from %llu to %llu(%zd)",
@@ -620,9 +722,9 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
if (pos > valid_size)
pos = valid_size;
- if (iocb_is_dsync(iocb) && iocb->ki_pos > pos) {
- ssize_t err = vfs_fsync_range(file, pos, iocb->ki_pos - 1,
- iocb->ki_flags & IOCB_SYNC);
+ if (iocb->ki_pos > pos) {
+ ssize_t err = generic_write_sync(iocb, iocb->ki_pos - pos);
+
if (err < 0)
return err;
}
@@ -635,24 +737,31 @@ unlock:
return ret;
}
+static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+
+ if (unlikely(exfat_forced_shutdown(inode->i_sb)))
+ return -EIO;
+
+ return generic_file_read_iter(iocb, iter);
+}
+
static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
int err;
- struct vm_area_struct *vma = vmf->vma;
- struct file *file = vma->vm_file;
- struct inode *inode = file_inode(file);
+ struct inode *inode = file_inode(vmf->vma->vm_file);
struct exfat_inode_info *ei = EXFAT_I(inode);
- loff_t start, end;
+ loff_t new_valid_size;
if (!inode_trylock(inode))
return VM_FAULT_RETRY;
- start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
- end = min_t(loff_t, i_size_read(inode),
- start + vma->vm_end - vma->vm_start);
+ new_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
+ new_valid_size = min(new_valid_size, i_size_read(inode));
- if (ei->valid_size < end) {
- err = exfat_extend_valid_size(file, end);
+ if (ei->valid_size < new_valid_size) {
+ err = exfat_extend_valid_size(inode, new_valid_size);
if (err < 0) {
inode_unlock(inode);
return vmf_fs_error(err);
@@ -670,25 +779,41 @@ static const struct vm_operations_struct exfat_file_vm_ops = {
.page_mkwrite = exfat_page_mkwrite,
};
-static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
+static int exfat_file_mmap_prepare(struct vm_area_desc *desc)
{
+ struct file *file = desc->file;
+
+ if (unlikely(exfat_forced_shutdown(file_inode(desc->file)->i_sb)))
+ return -EIO;
+
file_accessed(file);
- vma->vm_ops = &exfat_file_vm_ops;
+ desc->vm_ops = &exfat_file_vm_ops;
return 0;
}
+static ssize_t exfat_splice_read(struct file *in, loff_t *ppos,
+ struct pipe_inode_info *pipe, size_t len, unsigned int flags)
+{
+ if (unlikely(exfat_forced_shutdown(file_inode(in)->i_sb)))
+ return -EIO;
+
+ return filemap_splice_read(in, ppos, pipe, len, flags);
+}
+
const struct file_operations exfat_file_operations = {
.llseek = generic_file_llseek,
- .read_iter = generic_file_read_iter,
+ .read_iter = exfat_file_read_iter,
.write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl,
#endif
- .mmap = exfat_file_mmap,
+ .mmap_prepare = exfat_file_mmap_prepare,
.fsync = exfat_file_fsync,
- .splice_read = filemap_splice_read,
+ .splice_read = exfat_splice_read,
.splice_write = iter_file_splice_write,
+ .fallocate = exfat_fallocate,
+ .setlease = generic_setlease,
};
const struct inode_operations exfat_file_inode_operations = {