Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valid size #60

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions exfat_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ struct exfat_dir_entry {
unsigned char flags;
unsigned short attr;
loff_t size;
loff_t valid_size;
unsigned int num_subdirs;
struct timespec64 atime;
struct timespec64 mtime;
Expand Down Expand Up @@ -332,6 +333,7 @@ struct exfat_inode_info {
loff_t i_size_aligned;
/* on-disk position of directory entry or 0 */
loff_t i_pos;
loff_t valid_size;
/* hash by i_location */
struct hlist_node i_hash_fat;
/* protect bmap against truncate */
Expand Down
197 changes: 176 additions & 21 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,58 @@
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/msdos_fs.h>
#include <linux/writeback.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;
int ret;
unsigned int num_clusters, new_num_clusters, last_clu;
struct exfat_inode_info *ei = EXFAT_I(inode);
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_chain clu;

err = generic_cont_expand_simple(inode, size);
if (err)
return err;
ret = inode_newsize_ok(inode, size);
if (ret)
return ret;

num_clusters = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi);
new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi);

if (new_num_clusters == num_clusters)
goto out;

exfat_chain_set(&clu, ei->start_clu, num_clusters, ei->flags);
ret = exfat_find_last_cluster(sb, &clu, &last_clu);
if (ret)
return ret;

clu.dir = (last_clu == EXFAT_EOF_CLUSTER) ?
EXFAT_EOF_CLUSTER : last_clu + 1;
clu.size = 0;
clu.flags = ei->flags;

ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters,
&clu, IS_DIRSYNC(inode));
if (ret)
return ret;

/* Append new clusters to chain */
if (clu.flags != ei->flags) {
exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters);
ei->flags = ALLOC_FAT_CHAIN;
}
if (clu.flags == ALLOC_FAT_CHAIN)
if (exfat_ent_set(sb, last_clu, clu.dir))
goto free_clu;

if (num_clusters == 0)
ei->start_clu = clu.dir;

out:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
Expand All @@ -36,22 +74,23 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
#else
inode->i_ctime = inode->i_mtime = current_time(inode);
#endif
mark_inode_dirty(inode);
/* Expanded range not zeroed, do not update valid_size */
i_size_write(inode, size);

if (!IS_SYNC(inode))
return 0;
ei->i_size_aligned = round_up(size, sb->s_blocksize);
ei->i_size_ondisk = ei->i_size_aligned;
inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;

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;
if (IS_DIRSYNC(inode))
return write_inode_now(inode, 1);

mark_inode_dirty(inode);

return filemap_fdatawait_range(mapping, start, start + count - 1);
return 0;

free_clu:
exfat_free_cluster(inode, &clu);
return -EIO;
}

static bool exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode)
Expand Down Expand Up @@ -156,6 +195,9 @@ int __exfat_truncate(struct inode *inode)
ei->start_clu = EXFAT_EOF_CLUSTER;
}

if (i_size_read(inode) < ei->valid_size)
ei->valid_size = i_size_read(inode);

if (ei->type == TYPE_FILE)
ei->attr |= EXFAT_ATTR_ARCHIVE;

Expand Down Expand Up @@ -548,15 +590,128 @@ 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_file_zeroed_range(struct file *file, loff_t start, loff_t end)
{
int err;
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;

while (start < end) {
u32 zerofrom, len;
struct page *page = NULL;

zerofrom = start & (PAGE_SIZE - 1);
len = PAGE_SIZE - zerofrom;
if (start + len > end)
len = end - start;

err = ops->write_begin(file, mapping, start, len, &page, NULL);
if (err)
goto out;

zero_user_segment(page, zerofrom, zerofrom + len);

err = ops->write_end(file, mapping, start, len, len, page, NULL);
if (err < 0)
goto out;
start += len;

balance_dirty_pages_ratelimited(mapping);
cond_resched();
}

ei->valid_size = end;
mark_inode_dirty(inode);

out:
return err;
}

static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
{
ssize_t ret;
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = iocb->ki_pos;
loff_t valid_size;

inode_lock(inode);

valid_size = ei->valid_size;

ret = generic_write_checks(iocb, iter);
if (ret < 0)
goto unlock;

if (pos > valid_size) {
ret = exfat_file_zeroed_range(file, valid_size, pos);
if (ret < 0 && ret != -ENOSPC) {
exfat_err(inode->i_sb,
"write: fail to zero from %llu to %llu(%ld)",
valid_size, pos, ret);
}
if (ret < 0)
goto unlock;
}

ret = __generic_file_write_iter(iocb, iter);
if (ret < 0)
goto unlock;

inode_unlock(inode);

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 (err < 0)
return err;
}

return ret;

unlock:
inode_unlock(inode);

return ret;
}

static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
{
int ret;
struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
loff_t end = min_t(loff_t, i_size_read(inode),
start + vma->vm_end - vma->vm_start);

if ((vma->vm_flags & VM_WRITE) && ei->valid_size < end) {
ret = exfat_file_zeroed_range(file, ei->valid_size, end);
if (ret < 0) {
exfat_err(inode->i_sb,
"mmap: fail to zero from %llu to %llu(%d)",
start, end, ret);
return ret;
}
}

return generic_file_mmap(file, vma);
}

const struct file_operations exfat_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl,
#endif
.mmap = generic_file_mmap,
.mmap = exfat_file_mmap,
.fsync = exfat_file_fsync,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)
.splice_read = filemap_splice_read,
Expand Down
Loading
Loading