summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/Makefile2
-rw-r--r--fs/affs/affs.h2
-rw-r--r--fs/affs/amigaffs.c12
-rw-r--r--fs/affs/file.c25
-rw-r--r--fs/affs/inode.c13
-rw-r--r--fs/affs/namei.c9
-rw-r--r--fs/affs/super.c1
-rw-r--r--fs/afs/fsclient.c5
-rw-r--r--fs/afs/internal.h3
-rw-r--r--fs/attr.c2
-rw-r--r--fs/bfs/dir.c9
-rw-r--r--fs/bfs/inode.c30
-rw-r--r--fs/binfmt_elf.c10
-rw-r--r--fs/binfmt_elf_fdpic.c11
-rw-r--r--fs/binfmt_misc.c350
-rw-r--r--fs/binfmt_misc_bpf.c22
-rw-r--r--fs/buffer.c83
-rw-r--r--fs/ceph/dir.c2
-rw-r--r--fs/ceph/mdsmap.c2
-rw-r--r--fs/crypto/policy.c2
-rw-r--r--fs/d_path.c3
-rw-r--r--fs/dcache.c16
-rw-r--r--fs/erofs/ishare.c6
-rw-r--r--fs/erofs/zdata.c4
-rw-r--r--fs/eventpoll.c6
-rw-r--r--fs/exec.c84
-rw-r--r--fs/exfat/dir.c8
-rw-r--r--fs/exfat/namei.c27
-rw-r--r--fs/ext2/dir.c2
-rw-r--r--fs/ext2/ext2.h3
-rw-r--r--fs/ext2/file.c17
-rw-r--r--fs/ext2/inode.c49
-rw-r--r--fs/ext2/super.c1
-rw-r--r--fs/ext2/xattr.c18
-rw-r--r--fs/ext4/ext4.h14
-rw-r--r--fs/ext4/ext4_jbd2.c25
-rw-r--r--fs/ext4/fsync.c24
-rw-r--r--fs/ext4/inode.c102
-rw-r--r--fs/ext4/super.c16
-rw-r--r--fs/f2fs/dir.c1
-rw-r--r--fs/failfs.c166
-rw-r--r--fs/fat/dir.c6
-rw-r--r--fs/fat/fat.h1
-rw-r--r--fs/fat/file.c9
-rw-r--r--fs/fat/inode.c63
-rw-r--r--fs/fat/misc.c7
-rw-r--r--fs/fat/namei_msdos.c33
-rw-r--r--fs/fat/namei_vfat.c20
-rw-r--r--fs/fs-writeback.c34
-rw-r--r--fs/fuse/file.c20
-rw-r--r--fs/fuse/notify.c4
-rw-r--r--fs/gfs2/glock.c229
-rw-r--r--fs/gfs2/glock.h2
-rw-r--r--fs/gfs2/incore.h7
-rw-r--r--fs/gfs2/lock_dlm.c8
-rw-r--r--fs/gfs2/main.c1
-rw-r--r--fs/gfs2/ops_fstype.c5
-rw-r--r--fs/gfs2/quota.c4
-rw-r--r--fs/gfs2/super.c2
-rw-r--r--fs/gfs2/sys.c2
-rw-r--r--fs/gfs2/trace_gfs2.h1
-rw-r--r--fs/inode.c18
-rw-r--r--fs/internal.h4
-rw-r--r--fs/iomap/buffered-io.c6
-rw-r--r--fs/isofs/compress.c1
-rw-r--r--fs/isofs/dir.c31
-rw-r--r--fs/isofs/export.c10
-rw-r--r--fs/isofs/isofs.h3
-rw-r--r--fs/isofs/namei.c59
-rw-r--r--fs/libfs.c7
-rw-r--r--fs/lockd/nlm3xdr_gen.c2
-rw-r--r--fs/lockd/nlm3xdr_gen.h2
-rw-r--r--fs/lockd/nlm4xdr_gen.c47
-rw-r--r--fs/lockd/nlm4xdr_gen.h2
-rw-r--r--fs/lockd/share.h8
-rw-r--r--fs/lockd/svc4proc.c4
-rw-r--r--fs/lockd/svcproc.c4
-rw-r--r--fs/lockd/svcshare.c36
-rw-r--r--fs/minix/dir.c2
-rw-r--r--fs/minix/file.c9
-rw-r--r--fs/minix/inode.c52
-rw-r--r--fs/minix/minix.h1
-rw-r--r--fs/namei.c210
-rw-r--r--fs/namespace.c6
-rw-r--r--fs/netfs/buffered_read.c10
-rw-r--r--fs/netfs/internal.h1
-rw-r--r--fs/netfs/main.c7
-rw-r--r--fs/netfs/objects.c30
-rw-r--r--fs/netfs/read_pgpriv2.c3
-rw-r--r--fs/netfs/rolling_buffer.c22
-rw-r--r--fs/netfs/write_issue.c15
-rw-r--r--fs/nfsd/nfs4xdr_gen.c2
-rw-r--r--fs/nfsd/nfs4xdr_gen.h2
-rw-r--r--fs/nilfs2/Kconfig2
-rw-r--r--fs/nilfs2/Makefile2
-rw-r--r--fs/nilfs2/file.c40
-rw-r--r--fs/nilfs2/inode.c13
-rw-r--r--fs/nilfs2/iomap.c71
-rw-r--r--fs/nilfs2/iomap.h13
-rw-r--r--fs/ntfs/ea.c29
-rw-r--r--fs/ntfs/ea.h3
-rw-r--r--fs/ntfs/file.c5
-rw-r--r--fs/ntfs/inode.c15
-rw-r--r--fs/ntfs/lcnalloc.c7
-rw-r--r--fs/ntfs/namei.c4
-rw-r--r--fs/ntfs/reparse.c7
-rw-r--r--fs/ntfs/runlist.c7
-rw-r--r--fs/ntfs3/attrib.c157
-rw-r--r--fs/ntfs3/attrlist.c15
-rw-r--r--fs/ntfs3/dir.c91
-rw-r--r--fs/ntfs3/file.c64
-rw-r--r--fs/ntfs3/frecord.c168
-rw-r--r--fs/ntfs3/fslog.c37
-rw-r--r--fs/ntfs3/fsntfs.c26
-rw-r--r--fs/ntfs3/index.c1
-rw-r--r--fs/ntfs3/inode.c280
-rw-r--r--fs/ntfs3/namei.c55
-rw-r--r--fs/ntfs3/ntfs.h3
-rw-r--r--fs/ntfs3/ntfs_fs.h45
-rw-r--r--fs/ntfs3/super.c14
-rw-r--r--fs/open.c51
-rw-r--r--fs/pipe.c171
-rw-r--r--fs/proc/Kconfig3
-rw-r--r--fs/proc/Makefile2
-rw-r--r--fs/proc/internal.h2
-rw-r--r--fs/quota/Kconfig10
-rw-r--r--fs/quota/dquot.c81
-rw-r--r--fs/resctrl/ctrlmondata.c71
-rw-r--r--fs/resctrl/monitor.c78
-rw-r--r--fs/resctrl/rdtgroup.c123
-rw-r--r--fs/seq_file.c11
-rw-r--r--fs/smb/client/file.c37
-rw-r--r--fs/smb/client/fscache.c6
-rw-r--r--fs/smb/client/inode.c27
-rw-r--r--fs/smb/client/misc.c51
-rw-r--r--fs/smb/client/smb2ops.c2
-rw-r--r--fs/stat.c2
-rw-r--r--fs/udf/dir.c2
-rw-r--r--fs/udf/file.c11
-rw-r--r--fs/udf/inode.c190
-rw-r--r--fs/udf/super.c1
-rw-r--r--fs/udf/udfdecl.h2
-rw-r--r--fs/xfs/xfs_buf.c4
-rw-r--r--fs/xfs/xfs_qm.c4
144 files changed, 2728 insertions, 1649 deletions
diff --git a/fs/Makefile b/fs/Makefile
index 447a1f7c5732..5d3cd2bdcb9c 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -16,7 +16,7 @@ obj-y := open.o read_write.o file_table.o super.o \
stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
fs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \
kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \
- file_attr.o fserror.o nullfs.o
+ file_attr.o fserror.o nullfs.o failfs.o
obj-$(CONFIG_BUFFER_HEAD) += buffer.o mpage.o
obj-$(CONFIG_PROC_FS) += proc_namespace.o
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index d6b3393633f2..d1c506c1f310 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -44,7 +44,6 @@ struct affs_inode_info {
struct mutex i_link_lock; /* Protects internal inode access. */
struct mutex i_ext_lock; /* Protects internal inode access. */
#define i_hash_lock i_ext_lock
- struct mapping_metadata_bhs i_metadata_bhs;
u32 i_blkcnt; /* block count */
u32 i_extcnt; /* extended block count */
u32 *i_lc; /* linear cache of extended blocks */
@@ -152,7 +151,6 @@ extern bool affs_nofilenametruncate(const struct dentry *dentry);
extern int affs_check_name(const unsigned char *name, int len,
bool notruncate);
extern int affs_copy_name(unsigned char *bstr, struct dentry *dentry);
-struct mapping_metadata_bhs *affs_get_metadata_bhs(struct inode *inode);
/* bitmap. c */
diff --git a/fs/affs/amigaffs.c b/fs/affs/amigaffs.c
index bed4fc805e8e..6cc0fc9a4cbf 100644
--- a/fs/affs/amigaffs.c
+++ b/fs/affs/amigaffs.c
@@ -57,7 +57,7 @@ affs_insert_hash(struct inode *dir, struct buffer_head *bh)
AFFS_TAIL(sb, dir_bh)->hash_chain = cpu_to_be32(ino);
affs_adjust_checksum(dir_bh, ino);
- mmb_mark_buffer_dirty(dir_bh, &AFFS_I(dir)->i_metadata_bhs);
+ mark_buffer_dirty(dir_bh);
affs_brelse(dir_bh);
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
@@ -100,7 +100,7 @@ affs_remove_hash(struct inode *dir, struct buffer_head *rem_bh)
else
AFFS_TAIL(sb, bh)->hash_chain = ino;
affs_adjust_checksum(bh, be32_to_cpu(ino) - hash_ino);
- mmb_mark_buffer_dirty(bh, &AFFS_I(dir)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
AFFS_TAIL(sb, rem_bh)->parent = 0;
retval = 0;
break;
@@ -180,7 +180,7 @@ affs_remove_link(struct dentry *dentry)
affs_unlock_dir(dir);
goto done;
}
- mmb_mark_buffer_dirty(link_bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(link_bh);
memcpy(AFFS_TAIL(sb, bh)->name, AFFS_TAIL(sb, link_bh)->name, 32);
retval = affs_insert_hash(dir, bh);
@@ -188,7 +188,7 @@ affs_remove_link(struct dentry *dentry)
affs_unlock_dir(dir);
goto done;
}
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_unlock_dir(dir);
iput(dir);
@@ -203,7 +203,7 @@ affs_remove_link(struct dentry *dentry)
__be32 ino2 = AFFS_TAIL(sb, link_bh)->link_chain;
AFFS_TAIL(sb, bh)->link_chain = ino2;
affs_adjust_checksum(bh, be32_to_cpu(ino2) - link_ino);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
retval = 0;
/* Fix the link count, if bh is a normal header block without links */
switch (be32_to_cpu(AFFS_TAIL(sb, bh)->stype)) {
@@ -306,7 +306,7 @@ affs_remove_header(struct dentry *dentry)
retval = affs_remove_hash(dir, bh);
if (retval)
goto done_unlock;
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_unlock_dir(dir);
diff --git a/fs/affs/file.c b/fs/affs/file.c
index 144b17482d12..23e088a7ed4f 100644
--- a/fs/affs/file.c
+++ b/fs/affs/file.c
@@ -140,14 +140,14 @@ affs_alloc_extblock(struct inode *inode, struct buffer_head *bh, u32 ext)
AFFS_TAIL(sb, new_bh)->parent = cpu_to_be32(inode->i_ino);
affs_fix_checksum(sb, new_bh);
- mmb_mark_buffer_dirty(new_bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(new_bh);
tmp = be32_to_cpu(AFFS_TAIL(sb, bh)->extension);
if (tmp)
affs_warning(sb, "alloc_ext", "previous extension set (%x)", tmp);
AFFS_TAIL(sb, bh)->extension = cpu_to_be32(blocknr);
affs_adjust_checksum(bh, blocknr - tmp);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
AFFS_I(inode)->i_extcnt++;
mark_inode_dirty(inode);
@@ -581,7 +581,7 @@ affs_extent_file_ofs(struct inode *inode, u32 newsize)
memset(AFFS_DATA(bh) + boff, 0, tmp);
be32_add_cpu(&AFFS_DATA_HEAD(bh)->size, tmp);
affs_fix_checksum(sb, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
size += tmp;
bidx++;
} else if (bidx) {
@@ -603,7 +603,7 @@ affs_extent_file_ofs(struct inode *inode, u32 newsize)
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(tmp);
affs_fix_checksum(sb, bh);
bh->b_state &= ~(1UL << BH_New);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
if (prev_bh) {
u32 tmp_next = be32_to_cpu(AFFS_DATA_HEAD(prev_bh)->next);
@@ -613,8 +613,7 @@ affs_extent_file_ofs(struct inode *inode, u32 newsize)
bidx, tmp_next);
AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp_next);
- mmb_mark_buffer_dirty(prev_bh,
- &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(prev_bh);
affs_brelse(prev_bh);
}
size += bsize;
@@ -733,7 +732,7 @@ static int affs_write_end_ofs(const struct kiocb *iocb,
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(
max(boff + tmp, be32_to_cpu(AFFS_DATA_HEAD(bh)->size)));
affs_fix_checksum(sb, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
written += tmp;
from += tmp;
bidx++;
@@ -766,13 +765,12 @@ static int affs_write_end_ofs(const struct kiocb *iocb,
bidx, tmp_next);
AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp_next);
- mmb_mark_buffer_dirty(prev_bh,
- &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(prev_bh);
}
}
affs_brelse(prev_bh);
affs_fix_checksum(sb, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
written += bsize;
from += bsize;
bidx++;
@@ -801,14 +799,13 @@ static int affs_write_end_ofs(const struct kiocb *iocb,
bidx, tmp_next);
AFFS_DATA_HEAD(prev_bh)->next = cpu_to_be32(bh->b_blocknr);
affs_adjust_checksum(prev_bh, bh->b_blocknr - tmp_next);
- mmb_mark_buffer_dirty(prev_bh,
- &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(prev_bh);
}
} else if (be32_to_cpu(AFFS_DATA_HEAD(bh)->size) < tmp)
AFFS_DATA_HEAD(bh)->size = cpu_to_be32(tmp);
affs_brelse(prev_bh);
affs_fix_checksum(sb, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
written += tmp;
from += tmp;
bidx++;
@@ -945,7 +942,7 @@ affs_truncate(struct inode *inode)
}
AFFS_TAIL(sb, ext_bh)->extension = 0;
affs_fix_checksum(sb, ext_bh);
- mmb_mark_buffer_dirty(ext_bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(ext_bh);
affs_brelse(ext_bh);
if (inode->i_size) {
diff --git a/fs/affs/inode.c b/fs/affs/inode.c
index 5dd1b016bcb0..d4a3f381c4bc 100644
--- a/fs/affs/inode.c
+++ b/fs/affs/inode.c
@@ -206,7 +206,7 @@ affs_write_inode(struct inode *inode, struct writeback_control *wbc)
}
}
affs_fix_checksum(sb, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_brelse(bh);
affs_free_prealloc(inode);
return 0;
@@ -266,11 +266,8 @@ affs_evict_inode(struct inode *inode)
if (!inode->i_nlink) {
inode->i_size = 0;
affs_truncate(inode);
- } else {
- mmb_sync(&AFFS_I(inode)->i_metadata_bhs);
}
- mmb_invalidate(&AFFS_I(inode)->i_metadata_bhs);
clear_inode(inode);
affs_free_prealloc(inode);
cache_page = (unsigned long)AFFS_I(inode)->i_lc;
@@ -305,7 +302,7 @@ affs_new_inode(struct inode *dir)
bh = affs_getzeroblk(sb, block);
if (!bh)
goto err_bh;
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_brelse(bh);
inode->i_uid = current_fsuid();
@@ -393,17 +390,17 @@ affs_add_entry(struct inode *dir, struct inode *inode, struct dentry *dentry, s3
AFFS_TAIL(sb, bh)->link_chain = chain;
AFFS_TAIL(sb, inode_bh)->link_chain = cpu_to_be32(block);
affs_adjust_checksum(inode_bh, block - be32_to_cpu(chain));
- mmb_mark_buffer_dirty(inode_bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(inode_bh);
set_nlink(inode, 2);
ihold(inode);
}
affs_fix_checksum(sb, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
dentry->d_fsdata = (void *)(long)bh->b_blocknr;
affs_lock_dir(dir);
retval = affs_insert_hash(dir, bh);
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_unlock_dir(dir);
affs_unlock_link(inode);
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 5311828b19e8..6cb52efafe5f 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -373,7 +373,7 @@ affs_symlink(struct mnt_idmap *idmap, struct inode *dir,
}
*p = 0;
inode->i_size = i + 1;
- mmb_mark_buffer_dirty(bh, &AFFS_I(inode)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_brelse(bh);
mark_inode_dirty(inode);
@@ -443,8 +443,7 @@ affs_rename(struct inode *old_dir, struct dentry *old_dentry,
/* TODO: move it back to old_dir, if error? */
done:
- mmb_mark_buffer_dirty(bh,
- &AFFS_I(retval ? old_dir : new_dir)->i_metadata_bhs);
+ mark_buffer_dirty(bh);
affs_brelse(bh);
return retval;
}
@@ -497,8 +496,8 @@ affs_xrename(struct inode *old_dir, struct dentry *old_dentry,
retval = affs_insert_hash(old_dir, bh_new);
affs_unlock_dir(old_dir);
done:
- mmb_mark_buffer_dirty(bh_old, &AFFS_I(new_dir)->i_metadata_bhs);
- mmb_mark_buffer_dirty(bh_new, &AFFS_I(old_dir)->i_metadata_bhs);
+ mark_buffer_dirty(bh_old);
+ mark_buffer_dirty(bh_new);
affs_brelse(bh_old);
affs_brelse(bh_new);
return retval;
diff --git a/fs/affs/super.c b/fs/affs/super.c
index 4f331f784db2..ed8225b3c93d 100644
--- a/fs/affs/super.c
+++ b/fs/affs/super.c
@@ -108,7 +108,6 @@ static struct inode *affs_alloc_inode(struct super_block *sb)
i->i_lc = NULL;
i->i_ext_bh = NULL;
i->i_pa_cnt = 0;
- mmb_init(&i->i_metadata_bhs, &i->vfs_inode.i_data);
return &i->vfs_inode;
}
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
index a2ffd60889f8..1a3f186a6a11 100644
--- a/fs/afs/fsclient.c
+++ b/fs/afs/fsclient.c
@@ -477,6 +477,9 @@ void afs_fs_fetch_data(struct afs_operation *op)
if (!call)
return afs_op_nomem(op);
+ if (op->flags & AFS_OPERATION_ASYNC)
+ call->async = true;
+
/* marshall the parameters */
bp = call->request;
bp[0] = htonl(FSFETCHDATA);
@@ -484,7 +487,7 @@ void afs_fs_fetch_data(struct afs_operation *op)
bp[2] = htonl(vp->fid.vnode);
bp[3] = htonl(vp->fid.unique);
bp[4] = htonl(lower_32_bits(subreq->start + subreq->transferred));
- bp[5] = htonl(lower_32_bits(subreq->len + subreq->transferred));
+ bp[5] = htonl(lower_32_bits(subreq->len - subreq->transferred));
call->fid = vp->fid;
trace_afs_make_fs_call(call, &vp->fid);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 601f01e5c15f..290873bac89b 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -1421,7 +1421,7 @@ static inline void afs_make_op_call(struct afs_operation *op, struct afs_call *c
{
struct afs_addr_list *alist = op->estate->addresses;
- op->call = call;
+ op->call = afs_get_call(call, afs_call_trace_get);
op->type = call->type;
call->op = op;
call->key = op->key;
@@ -1429,6 +1429,7 @@ static inline void afs_make_op_call(struct afs_operation *op, struct afs_call *c
call->peer = rxrpc_kernel_get_peer(alist->addrs[op->addr_index].peer);
call->service_id = op->server->service_id;
afs_make_call(call, gfp);
+ afs_put_call(call);
}
static inline void afs_extract_begin(struct afs_call *call, void *buf, size_t size)
diff --git a/fs/attr.c b/fs/attr.c
index 4f437fabb7f0..71888ac903c2 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -176,7 +176,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,
* covered by the open-time check because sys_truncate() takes a
* path, not an open file.
*/
- if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
+ if (IS_VERITY(inode))
return -EPERM;
error = inode_newsize_ok(inode, attr->ia_size);
diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c
index 4c3b4db08cde..91a4871fa051 100644
--- a/fs/bfs/dir.c
+++ b/fs/bfs/dir.c
@@ -68,17 +68,10 @@ static int bfs_readdir(struct file *f, struct dir_context *ctx)
return 0;
}
-static int bfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
-{
- return mmb_fsync(file,
- &BFS_I(file->f_mapping->host)->i_metadata_bhs,
- start, end, datasync);
-}
-
const struct file_operations bfs_dir_operations = {
.read = generic_read_dir,
.iterate_shared = bfs_readdir,
- .fsync = bfs_fsync,
+ .fsync = simple_fsync,
.llseek = generic_file_llseek,
};
diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c
index e41efdd35db9..06e3a848b4ef 100644
--- a/fs/bfs/inode.c
+++ b/fs/bfs/inode.c
@@ -136,7 +136,6 @@ static int bfs_write_inode(struct inode *inode, struct writeback_control *wbc)
unsigned long i_sblock;
struct bfs_inode *di;
struct buffer_head *bh;
- int err = 0;
dprintf("ino=%08x\n", ino);
@@ -165,13 +164,31 @@ static int bfs_write_inode(struct inode *inode, struct writeback_control *wbc)
di->i_eoffset = cpu_to_le32(i_sblock * BFS_BSIZE + inode->i_size - 1);
mark_buffer_dirty(bh);
- if (wbc->sync_mode == WB_SYNC_ALL) {
- sync_dirty_buffer(bh);
- if (buffer_req(bh) && !buffer_uptodate(bh))
- err = -EIO;
- }
brelse(bh);
mutex_unlock(&info->bfs_lock);
+ set_inode_metadata_writeback(inode);
+ return 0;
+}
+
+static int bfs_sync_inode_metadata(struct inode *inode,
+ struct writeback_control *wbc)
+{
+ int err = 0;
+ struct bfs_inode *di;
+ struct buffer_head *bh;
+
+ di = find_inode(inode->i_sb, (u16)inode->i_ino, &bh);
+ if (IS_ERR(di))
+ return PTR_ERR(di);
+
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ err = -EIO;
+ goto out;
+ }
+ err = mmb_sync(&BFS_I(inode)->i_metadata_bhs);
+out:
+ brelse(bh);
return err;
}
@@ -302,6 +319,7 @@ static const struct super_operations bfs_sops = {
.alloc_inode = bfs_alloc_inode,
.free_inode = bfs_free_inode,
.write_inode = bfs_write_inode,
+ .sync_inode_metadata = bfs_sync_inode_metadata,
.evict_inode = bfs_evict_inode,
.put_super = bfs_put_super,
.statfs = bfs_statfs,
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index e3131a311995..06d0df105382 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -179,7 +179,6 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
unsigned char k_rand_bytes[16];
int items;
elf_addr_t *elf_info;
- elf_addr_t flags = 0;
int ei_index;
const struct cred *cred = current_cred();
struct vm_area_struct *vma;
@@ -254,9 +253,7 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
NEW_AUX_ENT(AT_BASE, interp_load_addr);
- if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0)
- flags |= AT_FLAGS_PRESERVE_ARGV0;
- NEW_AUX_ENT(AT_FLAGS, flags);
+ NEW_AUX_ENT(AT_FLAGS, bprm_at_flags(bprm));
NEW_AUX_ENT(AT_ENTRY, e_entry);
NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));
NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));
@@ -904,7 +901,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
goto out_free_interp;
- interpreter = open_exec(elf_interpreter);
+ interpreter = bprm_open_interpreter(bprm, elf_interpreter);
kfree(elf_interpreter);
retval = PTR_ERR(interpreter);
if (IS_ERR(interpreter))
@@ -935,6 +932,9 @@ out_free_interp:
goto out_free_ph;
}
+ /* No PT_INTERP to substitute for: the override does not apply. */
+ bprm_drop_loader(bprm);
+
elf_ppnt = elf_phdata;
for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++)
switch (elf_ppnt->p_type) {
diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c
index fe0b5c5ed2bc..068c46875c74 100644
--- a/fs/binfmt_elf_fdpic.c
+++ b/fs/binfmt_elf_fdpic.c
@@ -263,7 +263,8 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
kdebug("Using ELF interpreter %s", interpreter_name);
/* replace the program with the interpreter */
- interpreter = open_exec(interpreter_name);
+ interpreter = bprm_open_interpreter(bprm,
+ interpreter_name);
retval = PTR_ERR(interpreter);
if (IS_ERR(interpreter)) {
interpreter = NULL;
@@ -299,6 +300,9 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
}
+ /* No PT_INTERP to substitute for: the override does not apply. */
+ bprm_drop_loader(bprm);
+
if (is_constdisp(&exec_params.hdr))
exec_params.flags |= ELF_FDPIC_FLAG_CONSTDISP;
@@ -509,7 +513,6 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm,
char *k_platform, *k_base_platform;
char __user *u_platform, *u_base_platform, *p;
int loop;
- unsigned long flags = 0;
int ei_index;
elf_addr_t *elf_info;
@@ -649,9 +652,7 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm,
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec_params->hdr.e_phnum);
NEW_AUX_ENT(AT_BASE, interp_params->elfhdr_addr);
- if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0)
- flags |= AT_FLAGS_PRESERVE_ARGV0;
- NEW_AUX_ENT(AT_FLAGS, flags);
+ NEW_AUX_ENT(AT_FLAGS, bprm_at_flags(bprm));
NEW_AUX_ENT(AT_ENTRY, exec_params->entry_addr);
NEW_AUX_ENT(AT_UID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->uid));
NEW_AUX_ENT(AT_EUID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->euid));
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index c06cea00a6d6..707f8a14f8a6 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -10,6 +10,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/array_size.h>
#include <linux/binfmt_misc.h>
#include <linux/binfmts.h>
#include <linux/bitops.h>
@@ -49,8 +50,42 @@ enum binfmt_misc_entry_flags {
MISC_FMT_OPEN_BINARY = (1U << 30),
MISC_FMT_CREDENTIALS = (1U << 29),
MISC_FMT_OPEN_FILE = (1U << 28),
+ MISC_FMT_TRANSPARENT = (1U << 27),
+ MISC_FMT_LOADER = (1U << 26),
};
+/**
+ * struct binfmt_misc_flag - a flag character of the register string
+ * @c: the character userspace writes and reads back
+ * @flag: the entry flag it sets
+ * @implies: entry flags it turns on in addition
+ * @desc: what it does, for the registration debug output
+ */
+struct binfmt_misc_flag {
+ char c;
+ unsigned long flag;
+ unsigned long implies;
+ const char *desc;
+};
+
+static const struct binfmt_misc_flag misc_flags[] = {
+ { 'P', MISC_FMT_PRESERVE_ARGV0, 0, "preserve argv0" },
+ { 'O', MISC_FMT_OPEN_BINARY, 0, "open binary" },
+ { 'C', MISC_FMT_CREDENTIALS, MISC_FMT_OPEN_BINARY, "credentials from the binary" },
+ { 'F', MISC_FMT_OPEN_FILE, 0, "open interpreter file now" },
+ { 'T', MISC_FMT_TRANSPARENT, MISC_FMT_OPEN_BINARY, "transparent" },
+ { 'L', MISC_FMT_LOADER, 0, "loader substitution" },
+};
+
+/* Look up a flag character, NULL if @c is not one. */
+static const struct binfmt_misc_flag *misc_flag_by_char(const char c)
+{
+ for (int i = 0; i < ARRAY_SIZE(misc_flags); i++)
+ if (misc_flags[i].c == c)
+ return &misc_flags[i];
+ return NULL;
+}
+
struct binfmt_misc_entry {
struct hlist_node node;
unsigned long flags; /* type, status, etc. */
@@ -288,56 +323,97 @@ drop_staged:
return ERR_PTR(retval);
}
-/*
- * the loader itself
+/**
+ * entry_invocation_flags - the invocation flags in effect for this exec
+ * @e: matched binary type handler
+ * @bprm: binary that is being executed
+ *
+ * A static entry fixes its flags at registration, a 'B' entry's load program
+ * picks them per exec with bpf_binprm_set_flags(). Translate the latter into
+ * the former, implications included, so the dispatch has one set to act on.
+ *
+ * Return: the invocation flags for this exec
*/
-static int load_misc_binary(struct linux_binprm *bprm)
+static unsigned long entry_invocation_flags(const struct binfmt_misc_entry *e,
+ struct linux_binprm *bprm)
{
- struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL;
- const char *interpreter;
- struct file *interp_file;
- struct binfmt_misc *misc;
- bool preserve_argv0, want_execfd, want_creds;
- int retval;
+ unsigned long flags = 0;
+ u64 bpf_flags;
- misc = current_binfmt_misc();
- if (!READ_ONCE(misc->enabled))
- return -ENOEXEC;
+ if (!test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ return e->flags;
- fmt = get_binfmt_handler(misc, bprm);
- if (!fmt)
- return -ENOEXEC;
+ bpf_flags = bprm->bpf_flags;
+ /* Clear so they can't accumulate into a nested interpreter level. */
+ bprm->bpf_flags = 0;
- /* Need to be able to load the file after exec */
- if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
- return -ENOENT;
+ if (bpf_flags & BPF_BINPRM_PRESERVE_ARGV0)
+ flags |= MISC_FMT_PRESERVE_ARGV0;
+ if (bpf_flags & BPF_BINPRM_EXECFD)
+ flags |= MISC_FMT_OPEN_BINARY;
+ if (bpf_flags & BPF_BINPRM_CREDENTIALS)
+ flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY;
+ if (bpf_flags & BPF_BINPRM_TRANSPARENT)
+ flags |= MISC_FMT_TRANSPARENT | MISC_FMT_OPEN_BINARY;
+ if (bpf_flags & BPF_BINPRM_LOADER)
+ flags |= MISC_FMT_LOADER;
+
+ return flags;
+}
- interpreter = entry_select_interpreter(fmt, bprm);
- if (IS_ERR(interpreter))
- return PTR_ERR(interpreter);
+/**
+ * entry_open_interpreter - open the entry's interpreter for execution
+ * @e: matched binary type handler
+ * @interpreter: the interpreter selected for this exec
+ *
+ * An 'F' entry hands out a clone of the file it pre-opened at registration,
+ * any other entry opens the selected path.
+ *
+ * Return: the opened interpreter on success, an ERR_PTR on failure
+ */
+static struct file *entry_open_interpreter(const struct binfmt_misc_entry *e,
+ const char *interpreter)
+{
+ struct file *interp_file __free(fput) = NULL;
+ int retval;
- /*
- * The invocation flags are fixed at registration for a static handler
- * and chosen per exec by the load program, via bpf_binprm_set_flags(),
- * for a bpf one.
- */
- if (test_bit(MISC_FMT_BPF_BIT, &fmt->flags)) {
- u64 f = bprm->bpf_flags;
+ if (!(e->flags & MISC_FMT_OPEN_FILE))
+ return open_exec(interpreter);
+
+ interp_file = file_clone_open(e->interp_file);
+ if (IS_ERR(interp_file))
+ return interp_file;
- /* Clear so it can't accumulate into a nested interpreter level. */
- bprm->bpf_flags = 0;
+ retval = exe_file_deny_write_access(interp_file);
+ if (retval)
+ return ERR_PTR(retval);
- preserve_argv0 = f & BPF_BINPRM_PRESERVE_ARGV0;
- want_creds = f & BPF_BINPRM_CREDENTIALS;
- want_execfd = f & (BPF_BINPRM_CREDENTIALS | BPF_BINPRM_EXECFD);
- } else {
- preserve_argv0 = fmt->flags & MISC_FMT_PRESERVE_ARGV0;
- want_creds = fmt->flags & MISC_FMT_CREDENTIALS;
- want_execfd = fmt->flags & MISC_FMT_OPEN_BINARY;
- }
+ return no_free_ptr(interp_file);
+}
+
+/**
+ * build_interp_argv - splice the interpreter invocation into the argv
+ * @bprm: binary that is being executed
+ * @interpreter: the interpreter selected for this exec
+ * @flags: invocation flags in effect for this exec
+ *
+ * The interpreter becomes argv[0] and the binary its last argument, with an
+ * optional staged argument in between. The caller's argv[0] is dropped
+ * unless 'P' keeps it.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int build_interp_argv(struct linux_binprm *bprm, const char *interpreter,
+ unsigned long flags)
+{
+ int retval;
+
+ /* The interpreter has to be able to load the binary by path. */
+ if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
+ return -ENOENT;
/* The entry's own choice - not one accumulated from an earlier level. */
- if (preserve_argv0) {
+ if (flags & MISC_FMT_PRESERVE_ARGV0) {
bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;
} else {
retval = remove_arg_zero(bprm);
@@ -371,31 +447,83 @@ static int load_misc_binary(struct linux_binprm *bprm)
return retval;
bprm->argc++;
- /* Update interp in case binfmt_script needs it. */
- retval = bprm_change_interp(interpreter, bprm);
- if (retval < 0)
- return retval;
+ return 0;
+}
- if (fmt->flags & MISC_FMT_OPEN_FILE) {
- interp_file = file_clone_open(fmt->interp_file);
- if (!IS_ERR(interp_file)) {
- int err = exe_file_deny_write_access(interp_file);
+/*
+ * the loader itself
+ */
+static int load_misc_binary(struct linux_binprm *bprm)
+{
+ struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL;
+ const char *interpreter;
+ struct file *interp_file;
+ struct binfmt_misc *misc;
+ unsigned long flags;
+ int retval;
+
+ /* Only binfmt_misc stages one and exec_binprm() clears it per round. */
+ WARN_ON_ONCE(bprm->loader);
+
+ misc = current_binfmt_misc();
+ if (!READ_ONCE(misc->enabled))
+ return -ENOEXEC;
+
+ fmt = get_binfmt_handler(misc, bprm);
+ if (!fmt)
+ return -ENOEXEC;
+
+ interpreter = entry_select_interpreter(fmt, bprm);
+ if (IS_ERR(interpreter))
+ return PTR_ERR(interpreter);
- if (err) {
- fput(interp_file);
- interp_file = ERR_PTR(err);
- }
+ flags = entry_invocation_flags(fmt, bprm);
+
+ /* No argv is built for a staged argument to land in. */
+ if ((flags & (MISC_FMT_LOADER | MISC_FMT_TRANSPARENT)) &&
+ bprm->bpf_interp_arg)
+ return -EINVAL;
+
+ /*
+ * Stash the interpreter for binfmt_elf to consume in place of the
+ * binary's PT_INTERP and decline the match, so the search continues
+ * to the real format in the same round.
+ */
+ if (flags & MISC_FMT_LOADER) {
+ interp_file = entry_open_interpreter(fmt, interpreter);
+ if (IS_ERR(interp_file)) {
+ retval = PTR_ERR(interp_file);
+ /* Declining here would run the binary's own PT_INTERP. */
+ return retval == -ENOEXEC ? -EACCES : retval;
}
- } else {
- interp_file = open_exec(interpreter);
+
+ bprm->loader = interp_file;
+ return -ENOEXEC;
+ }
+
+ if (!(flags & MISC_FMT_TRANSPARENT)) {
+ retval = build_interp_argv(bprm, interpreter, flags);
+ if (retval)
+ return retval;
}
+
+ /* Update interp for the next round; sched_prepare_exec reports it. */
+ retval = bprm_change_interp(interpreter, bprm);
+ if (retval < 0)
+ return retval;
+
+ interp_file = entry_open_interpreter(fmt, interpreter);
if (IS_ERR(interp_file))
return PTR_ERR(interp_file);
+ /* Raise only past the last failure, or an -ENOEXEC decline leaks it. */
+ if (flags & MISC_FMT_TRANSPARENT)
+ bprm->interp_flags |= BINPRM_FLAGS_TRANSPARENT_INTERP;
+
bprm->interpreter = interp_file;
- if (want_execfd)
+ if (flags & MISC_FMT_OPEN_BINARY)
bprm->have_execfd = 1;
- if (want_creds)
+ if (flags & MISC_FMT_CREDENTIALS)
bprm->execfd_creds = 1;
return 0;
}
@@ -424,30 +552,16 @@ static char *scanarg(char *s, char del)
return s;
}
+/* Parse the 'flags' field, stopping at the first character that is not one. */
static char *check_special_flags(char *p, struct binfmt_misc_entry *e)
{
for (;; p++) {
- switch (*p) {
- case 'P':
- pr_debug("register: flag: P (preserve argv0)\n");
- e->flags |= MISC_FMT_PRESERVE_ARGV0;
- break;
- case 'O':
- pr_debug("register: flag: O (open binary)\n");
- e->flags |= MISC_FMT_OPEN_BINARY;
- break;
- case 'C':
- pr_debug("register: flag: C (preserve creds)\n");
- /* C implies O */
- e->flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY;
- break;
- case 'F':
- pr_debug("register: flag: F: open interpreter file now\n");
- e->flags |= MISC_FMT_OPEN_FILE;
- break;
- default:
+ const struct binfmt_misc_flag *f = misc_flag_by_char(*p);
+
+ if (!f)
return p;
- }
+ pr_debug("register: flag: %c (%s)\n", f->c, f->desc);
+ e->flags |= f->flag | f->implies;
}
}
@@ -570,7 +684,7 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
size_t count)
{
struct binfmt_misc_entry *e __free(kfree) = NULL;
- char *buf, *p;
+ char *buf, *p, *flags;
char del;
pr_debug("register: received %zu bytes\n", count);
@@ -595,7 +709,7 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
pr_debug("register: delim: %#x {%c}\n", del, del);
/* A flag-char delimiter runs the flag scan off the buffer. */
- if (del == 'P' || del == 'O' || del == 'C' || del == 'F')
+ if (misc_flag_by_char(del))
return ERR_PTR(-EINVAL);
/* Pad the buffer with the delim to simplify parsing below. */
@@ -666,21 +780,38 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer,
}
/* Parse the 'flags' field. */
+ flags = p;
p = check_special_flags(p, e);
+
+ /*
+ * A bpf handler decides the invocation flags per exec with
+ * bpf_binprm_set_flags() rather than fixing them at registration, and
+ * 'F' (pre-open a fixed interpreter) is meaningless for it, so a 'B'
+ * entry's flags field has to be empty.
+ */
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags) && p != flags)
+ return ERR_PTR(-EINVAL);
+
+ /* Transparency preserves the whole argv, argv[0] included. */
+ if ((e->flags & MISC_FMT_TRANSPARENT) &&
+ (e->flags & MISC_FMT_PRESERVE_ARGV0))
+ return ERR_PTR(-EINVAL);
+
+ /* A native exec splices no argv, passes no execfd and needs no creds. */
+ if ((e->flags & MISC_FMT_LOADER) &&
+ (e->flags & (MISC_FMT_TRANSPARENT | MISC_FMT_PRESERVE_ARGV0 |
+ MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY)))
+ return ERR_PTR(-EINVAL);
+
if (*p == '\n')
p++;
if (p != buf + count)
return ERR_PTR(-EINVAL);
- /*
- * A bpf handler decides the invocation flags per exec with
- * bpf_binprm_set_flags() rather than fixing them at registration, so a
- * 'B' entry carries no flags: 'P', 'C' and 'O' become per-exec choices
- * and 'F' (pre-open a fixed interpreter) is meaningless for it.
- */
- if (test_bit(MISC_FMT_BPF_BIT, &e->flags) &&
- (e->flags & (MISC_FMT_PRESERVE_ARGV0 | MISC_FMT_OPEN_BINARY |
- MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_FILE)))
+ /* Non-F opens the interp at exec against the caller's cwd; require absolute. */
+ if ((e->flags & (MISC_FMT_LOADER | MISC_FMT_CREDENTIALS)) &&
+ !(e->flags & MISC_FMT_OPEN_FILE) &&
+ e->interpreter[0] != '/')
return ERR_PTR(-EINVAL);
return no_free_ptr(e);
@@ -743,14 +874,9 @@ static int bm_entry_show(struct seq_file *m, void *unused)
/* print the special flags */
seq_puts(m, "flags: ");
- if (e->flags & MISC_FMT_PRESERVE_ARGV0)
- seq_putc(m, 'P');
- if (e->flags & MISC_FMT_OPEN_BINARY)
- seq_putc(m, 'O');
- if (e->flags & MISC_FMT_CREDENTIALS)
- seq_putc(m, 'C');
- if (e->flags & MISC_FMT_OPEN_FILE)
- seq_putc(m, 'F');
+ for (int i = 0; i < ARRAY_SIZE(misc_flags); i++)
+ if (e->flags & misc_flags[i].flag)
+ seq_putc(m, misc_flags[i].c);
seq_putc(m, '\n');
if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
@@ -1099,18 +1225,9 @@ static const struct file_operations bm_status_operations = {
/* Superblock handling */
-static void bm_put_super(struct super_block *sb)
-{
- struct user_namespace *user_ns = sb->s_fs_info;
-
- sb->s_fs_info = NULL;
- put_user_ns(user_ns);
-}
-
static const struct super_operations bm_super_ops = {
.statfs = simple_statfs,
.evict_inode = bm_evict_inode,
- .put_super = bm_put_super,
};
static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
@@ -1127,6 +1244,10 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
if (WARN_ON(user_ns != current_user_ns()))
return -EINVAL;
+ /* Never exec off this instance and never let anything stack on it. */
+ sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
+ sb->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
+
/*
* Lazily allocate a new binfmt_misc instance for this namespace, i.e.
* do it here during the first mount of binfmt_misc. We don't need to
@@ -1164,13 +1285,12 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
/*
* When the binfmt_misc superblock for this userns is shutdown
* ->enabled might have been set to false and we don't reinitialize
- * ->enabled again in put_super() as someone might already be mounting
- * binfmt_misc again. It also would be pointless since by the time
- * ->put_super() is called we know that the binary type list for this
- * bintfmt_misc mount is empty making load_misc_binary() return
- * -ENOEXEC independent of whether ->enabled is true. Instead, if
- * someone mounts binfmt_misc for the first time or again we simply
- * reset ->enabled to true.
+ * ->enabled again during shutdown as someone might already be mounting
+ * binfmt_misc again. It also would be pointless since by then we know
+ * that the binary type list for this binfmt_misc mount is empty making
+ * load_misc_binary() return -ENOEXEC independent of whether ->enabled
+ * is true. Instead, if someone mounts binfmt_misc for the first time or
+ * again we simply reset ->enabled to true.
*/
WRITE_ONCE(misc->enabled, true);
@@ -1199,6 +1319,14 @@ static const struct fs_context_operations bm_context_ops = {
.get_tree = bm_get_tree,
};
+static void bm_kill_sb(struct super_block *sb)
+{
+ struct user_namespace *user_ns = sb->s_fs_info;
+
+ kill_anon_super(sb);
+ put_user_ns(user_ns);
+}
+
static int bm_init_fs_context(struct fs_context *fc)
{
fc->ops = &bm_context_ops;
@@ -1215,7 +1343,7 @@ static struct file_system_type bm_fs_type = {
.name = "binfmt_misc",
.init_fs_context = bm_init_fs_context,
.fs_flags = FS_USERNS_MOUNT,
- .kill_sb = kill_anon_super,
+ .kill_sb = bm_kill_sb,
};
MODULE_ALIAS_FS("binfmt_misc");
diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c
index 65a3b8313fbe..92003ea640d3 100644
--- a/fs/binfmt_misc_bpf.c
+++ b/fs/binfmt_misc_bpf.c
@@ -174,19 +174,33 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm,
* @flags: an OR of enum bpf_binprm_flags values
*
* To be called from the load program of a struct binfmt_misc_ops handler. It
- * decides per exec what a static entry fixes at registration with the P, C and
- * O flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0],
+ * decides per exec what a static entry fixes at registration with the P, C,
+ * O, T and L flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0],
* BPF_BINPRM_CREDENTIALS computes credentials from the binary, and
* BPF_BINPRM_EXECFD hands the binary to the interpreter through AT_EXECFD.
+ * BPF_BINPRM_TRANSPARENT additionally leaves the argument vector untouched,
+ * making the exec look like a direct execution of the binary.
+ * BPF_BINPRM_LOADER substitutes the interpreter for the binary's PT_INTERP
+ * and runs the binary as a native exec; it excludes every other flag.
* Calling it again replaces the flags, passing zero clears them again.
*
- * Return: 0 on success, -EINVAL if @flags contains an unknown bit
+ * Return: 0 on success, -EINVAL if @flags contains an unknown bit or an
+ * invalid combination
*/
__bpf_kfunc int bpf_binprm_set_flags(struct linux_binprm *bprm,
enum bpf_binprm_flags flags)
{
if (flags & ~(BPF_BINPRM_PRESERVE_ARGV0 | BPF_BINPRM_CREDENTIALS |
- BPF_BINPRM_EXECFD))
+ BPF_BINPRM_EXECFD | BPF_BINPRM_TRANSPARENT |
+ BPF_BINPRM_LOADER))
+ return -EINVAL;
+
+ /* Loader substitution is a native exec: no splice, execfd or creds work. */
+ if ((flags & BPF_BINPRM_LOADER) && (flags & ~BPF_BINPRM_LOADER))
+ return -EINVAL;
+
+ /* Transparency preserves the whole argv, argv[0] included. */
+ if ((flags & BPF_BINPRM_TRANSPARENT) && (flags & BPF_BINPRM_PRESERVE_ARGV0))
return -EINVAL;
bprm->bpf_flags = flags;
diff --git a/fs/buffer.c b/fs/buffer.c
index b62e7de421b5..dd50d17b8907 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -599,79 +599,6 @@ int mmb_sync(struct mapping_metadata_bhs *mmb)
}
EXPORT_SYMBOL(mmb_sync);
-/**
- * mmb_fsync_noflush - fsync implementation for simple filesystems with
- * metadata buffers list
- *
- * @file: file to synchronize
- * @mmb: list of metadata bhs to flush
- * @start: start offset in bytes
- * @end: end offset in bytes (inclusive)
- * @datasync: only synchronize essential metadata if true
- *
- * This is an implementation of the fsync method for simple filesystems which
- * track all non-inode metadata in the buffers list hanging off the @mmb
- * structure.
- */
-int mmb_fsync_noflush(struct file *file, struct mapping_metadata_bhs *mmb,
- loff_t start, loff_t end, bool datasync)
-{
- struct inode *inode = file->f_mapping->host;
- int err;
- int ret = 0;
-
- err = file_write_and_wait_range(file, start, end);
- if (err)
- return err;
-
- if (mmb)
- ret = mmb_sync(mmb);
- if (!(inode_state_read_once(inode) & I_DIRTY_ALL))
- goto out;
- if (datasync && !(inode_state_read_once(inode) & I_DIRTY_DATASYNC))
- goto out;
-
- err = sync_inode_metadata(inode, 1);
- if (ret == 0)
- ret = err;
-
-out:
- /* check and advance again to catch errors after syncing out buffers */
- err = file_check_and_advance_wb_err(file);
- if (ret == 0)
- ret = err;
- return ret;
-}
-EXPORT_SYMBOL(mmb_fsync_noflush);
-
-/**
- * mmb_fsync - fsync implementation for simple filesystems with metadata
- * buffers list
- *
- * @file: file to synchronize
- * @mmb: list of metadata bhs to flush
- * @start: start offset in bytes
- * @end: end offset in bytes (inclusive)
- * @datasync: only synchronize essential metadata if true
- *
- * This is an implementation of the fsync method for simple filesystems which
- * track all non-inode metadata in the buffers list hanging off the @mmb
- * structure. This also makes sure that a device cache flush operation is
- * called at the end.
- */
-int mmb_fsync(struct file *file, struct mapping_metadata_bhs *mmb,
- loff_t start, loff_t end, bool datasync)
-{
- struct inode *inode = file->f_mapping->host;
- int ret;
-
- ret = mmb_fsync_noflush(file, mmb, start, end, datasync);
- if (!ret)
- ret = blkdev_issue_flush(inode->i_sb->s_bdev);
- return ret;
-}
-EXPORT_SYMBOL(mmb_fsync);
-
/*
* Called when we've recently written block `bblock', and it is known that
* `bblock' was for a buffer_boundary() buffer. This means that the block at
@@ -1094,12 +1021,18 @@ EXPORT_SYMBOL(mark_buffer_dirty);
void mark_buffer_write_io_error(struct buffer_head *bh)
{
+ struct mapping_metadata_bhs *mmb;
+
set_buffer_write_io_error(bh);
/* FIXME: do we need to set this in both places? */
if (bh->b_folio && bh->b_folio->mapping)
mapping_set_error(bh->b_folio->mapping, -EIO);
- if (bh->b_mmb)
- mapping_set_error(bh->b_mmb->mapping, -EIO);
+ /* Protect us from mmb & inode getting freed while we work on it */
+ rcu_read_lock();
+ mmb = READ_ONCE(bh->b_mmb);
+ if (mmb)
+ mapping_set_error(mmb->mapping, -EIO);
+ rcu_read_unlock();
}
EXPORT_SYMBOL(mark_buffer_write_io_error);
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index dfa590a220ea..4a5e0290f2e3 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -1672,7 +1672,7 @@ __dentry_leases_walk(struct ceph_mds_client *mdsc,
if (!spin_trylock(&dentry->d_lock))
continue;
- if (__lockref_is_dead(&dentry->d_lockref)) {
+ if (lockref_is_dead(&dentry->d_lockref)) {
list_del_init(&di->lease_list);
goto next;
}
diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c
index d8e46eb7e5eb..450a4dc9662e 100644
--- a/fs/ceph/mdsmap.c
+++ b/fs/ceph/mdsmap.c
@@ -15,7 +15,7 @@
#include "super.h"
#define CEPH_MDS_IS_READY(i, ignore_laggy) \
- (m->m_info[i].state > 0 && ignore_laggy ? true : !m->m_info[i].laggy)
+ (m->m_info[i].state > 0 && (ignore_laggy ? true : !m->m_info[i].laggy))
static int __mdsmap_get_random_mds(struct ceph_mdsmap *m, bool ignore_laggy)
{
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index a7322dba7557..6dd510f93e6d 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -538,7 +538,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
size - 1))
return -EFAULT;
- if (!inode_owner_or_capable(&nop_mnt_idmap, inode))
+ if (!inode_owner_or_capable(file_mnt_idmap(filp), inode))
return -EACCES;
ret = mnt_want_write_file(filp);
diff --git a/fs/d_path.c b/fs/d_path.c
index a48957c0971e..c25309006d5d 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -279,7 +279,8 @@ char *d_path(const struct path *path, char *buf, int buflen)
* and instead have d_path return the mounted path.
*/
if (path->dentry->d_op && path->dentry->d_op->d_dname &&
- (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root))
+ (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root ||
+ failfs_mnt(path->mnt)))
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
rcu_read_lock();
diff --git a/fs/dcache.c b/fs/dcache.c
index 3e9af9de7074..2aee85f3fbaa 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -434,7 +434,7 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry)
static void dentry_free(struct dentry *dentry)
{
DENTRY_WARN_ONCE(d_really_is_positive(dentry), dentry);
- DENTRY_WARN_ONCE(dentry->d_lockref.count >= 0, dentry);
+ DENTRY_WARN_ONCE(!lockref_is_dead(&dentry->d_lockref), dentry);
D_FLAG_VERIFY(dentry, 0);
if (unlikely(dname_external(dentry))) {
struct external_name *p = external_name(dentry);
@@ -782,7 +782,7 @@ static bool lock_for_kill(struct dentry *dentry)
*
* If @dentry is idle and remains such after we assemble the full
* locking environment for eviction (see lock_for_kill() for details)
- * we mark it doomed (->d_lockref.count < 0) and proceed to detaching
+ * we mark it doomed (see lockref_mark_dead()) and proceed to detaching
* it from any filesystem objects. Otherwise we drop ->d_lock and
* return %NULL.
*
@@ -946,7 +946,7 @@ static inline bool fast_dput(struct dentry *dentry)
if (unlikely(ret < 0)) {
spin_lock(&dentry->d_lock);
rcu_read_unlock();
- if (WARN_ON_ONCE(dentry->d_lockref.count <= 0)) {
+ if (WARN_ON_ONCE(lockref_is_dead_or_zero(&dentry->d_lockref))) {
spin_unlock(&dentry->d_lock);
return true;
}
@@ -1644,7 +1644,7 @@ static enum d_walk_ret select_collect(void *_data, struct dentry *dentry)
if (data->start == dentry)
goto out;
- if (dentry->d_lockref.count <= 0) {
+ if (lockref_is_dead_or_zero(&dentry->d_lockref)) {
__move_to_shrink_list(dentry, &data->dispose);
data->found++;
}
@@ -1676,7 +1676,7 @@ static enum d_walk_ret select_collect2(void *_data, struct dentry *dentry)
if (data->start == dentry)
goto out;
- if (dentry->d_lockref.count <= 0) {
+ if (lockref_is_dead_or_zero(&dentry->d_lockref)) {
if (!__move_to_shrink_list(dentry, &data->dispose)) {
/*
* We need an enter RCU read-side critical area that
@@ -1747,7 +1747,7 @@ static void shrink_dcache_tree(struct dentry *parent, bool for_umount)
spin_lock(&v->d_lock);
rcu_read_unlock();
- if (unlikely(v->d_lockref.count < 0)) {
+ if (unlikely(lockref_is_dead(&v->d_lockref))) {
// It's doomed; if it isn't dead yet, notify us
// once it becomes invisible to d_walk().
need_wait = d_add_waiter(v, &wait);
@@ -1823,7 +1823,7 @@ void shrink_dcache_for_umount(struct super_block *sb)
spin_unlock(&sb->s_roots_lock);
spin_lock(&dentry->d_lock);
rcu_read_unlock();
- if (unlikely(dentry->d_lockref.count < 0)) {
+ if (unlikely(lockref_is_dead(&dentry->d_lockref))) {
struct completion_list wait;
bool need_wait = d_add_waiter(dentry, &wait);
@@ -2822,7 +2822,7 @@ retry:
spin_lock(&dentry->d_lock);
rcu_read_unlock();
/* now we can try to grab a reference */
- if (unlikely(dentry->d_lockref.count < 0)) {
+ if (unlikely(lockref_is_dead(&dentry->d_lockref))) {
spin_unlock(&dentry->d_lock);
goto retry;
}
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
index 51b81aaf9ac3..fa7d4112dec5 100644
--- a/fs/erofs/ishare.c
+++ b/fs/erofs/ishare.c
@@ -69,10 +69,8 @@ bool erofs_ishare_fill_inode(struct inode *inode)
}
}
sd = d_obtain_alias(si); /* disconnected denties for sharedinodes */
- if (IS_ERR(sd)) {
- iput(si);
+ if (IS_ERR(sd))
return false;
- }
vi->sharedentry = sd;
INIT_LIST_HEAD(&vi->ishare_list);
spin_lock(&EROFS_I(si)->ishare_lock);
@@ -106,7 +104,7 @@ static int erofs_ishare_file_open(struct inode *inode, struct file *file)
if (file->f_flags & O_DIRECT)
return -EINVAL;
- rf = backing_file_open(file, file->f_flags | O_NOATIME | FMODE_NONOTIFY,
+ rf = backing_file_open(file, file->f_flags | O_NOATIME,
&sharedpath, current_cred());
if (IS_ERR(rf))
return PTR_ERR(rf);
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index 74520e910259..d022d1dff5a1 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -725,7 +725,7 @@ static bool z_erofs_get_pcluster(struct z_erofs_pcluster *pcl)
return true;
spin_lock(&pcl->lockref.lock);
- if (__lockref_is_dead(&pcl->lockref)) {
+ if (lockref_is_dead(&pcl->lockref)) {
spin_unlock(&pcl->lockref.lock);
return false;
}
@@ -945,7 +945,7 @@ static void z_erofs_put_pcluster(struct erofs_sb_info *sbi,
if (lockref_put_or_lock(&pcl->lockref))
return;
- DBG_BUGON(__lockref_is_dead(&pcl->lockref));
+ DBG_BUGON(lockref_is_dead(&pcl->lockref));
if (!--pcl->lockref.count) {
if (try_free && xa_trylock(&sbi->managed_pslots)) {
free = __erofs_try_to_release_pcluster(sbi, pcl);
diff --git a/fs/eventpoll.c b/fs/eventpoll.c
index eed8cecd94e3..e0c4bf88a838 100644
--- a/fs/eventpoll.c
+++ b/fs/eventpoll.c
@@ -2264,7 +2264,6 @@ static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
lockdep_assert_irqs_enabled();
if (timeout && (timeout->tv_sec | timeout->tv_nsec)) {
- slack = select_estimate_accuracy(timeout);
to = &expires;
*to = timespec64_to_ktime(*timeout);
} else if (timeout) {
@@ -2343,10 +2342,13 @@ static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
spin_unlock_irq(&ep->lock);
- if (!eavail)
+ if (!eavail) {
+ if (to)
+ slack = select_estimate_accuracy(timeout);
timed_out = !ep_schedule_timeout(to) ||
!schedule_hrtimeout_range(to, slack,
HRTIMER_MODE_ABS);
+ }
__set_current_state(TASK_RUNNING);
/*
diff --git a/fs/exec.c b/fs/exec.c
index 41e1684d999c..856731f78d05 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1102,6 +1102,17 @@ void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
}
/*
+ * The file the process presents as: its exe link and comm. A transparent
+ * dispatch presents as the binary, which is bprm->executable.
+ */
+static struct file *bprm_identity_file(const struct linux_binprm *bprm)
+{
+ if (bprm->interp_flags & BINPRM_FLAGS_TRANSPARENT_INTERP)
+ return bprm->executable;
+ return bprm->file;
+}
+
+/*
* Calling this is the point of no return. None of the failures will be
* seen by userspace since either the process is already taking a fatal
* signal (via de_thread() or coredump), or will have SEGV raised
@@ -1112,6 +1123,10 @@ int begin_new_exec(struct linux_binprm * bprm)
struct task_struct *me = current;
int retval;
+ /* A pending PT_INTERP substitution this format cannot consume. */
+ if (bprm->loader)
+ return -ENOEXEC;
+
/* Once we are committed compute the creds */
retval = bprm_creds_from_file(bprm);
if (retval)
@@ -1151,7 +1166,7 @@ int begin_new_exec(struct linux_binprm * bprm)
* not visible until then. Doing it here also ensures
* we don't race against replace_mm_exe_file().
*/
- retval = set_mm_exe_file(bprm->mm, bprm->file);
+ retval = set_mm_exe_file(bprm->mm, bprm_identity_file(bprm));
if (retval)
goto out;
@@ -1241,6 +1256,8 @@ int begin_new_exec(struct linux_binprm * bprm)
* Let's fix it up to be something reasonable.
*/
if (bprm->comm_from_dentry) {
+ struct file *comm_file = bprm_identity_file(bprm);
+
/*
* Hold RCU lock to keep the name from being freed behind our back.
* Use acquire semantics to make sure the terminating NUL from
@@ -1250,7 +1267,7 @@ int begin_new_exec(struct linux_binprm * bprm)
* detecting a concurrent rename and just want a terminated name.
*/
rcu_read_lock();
- __set_task_comm(me, smp_load_acquire(&bprm->file->f_path.dentry->d_name.name),
+ __set_task_comm(me, smp_load_acquire(&comm_file->f_path.dentry->d_name.name),
true);
rcu_read_unlock();
} else {
@@ -1291,10 +1308,17 @@ int begin_new_exec(struct linux_binprm * bprm)
/* Pass the opened binary to the interpreter. */
if (bprm->have_execfd) {
- retval = FD_ADD(0, bprm->executable);
- if (retval < 0)
- goto out_unlock;
+ struct file *executable = bprm->executable;
+
+ /* mm->exe_file carries its own write denial now so drop it. */
+ exe_file_allow_write_access(executable);
bprm->executable = NULL;
+ retval = FD_ADD(0, executable);
+ if (retval < 0) {
+ /* The reference was not consumed. */
+ fput(executable);
+ goto out_unlock;
+ }
bprm->execfd = retval;
}
return 0;
@@ -1394,6 +1418,39 @@ static void do_close_execat(struct file *file)
fput(file);
}
+/**
+ * bprm_open_interpreter - open the interpreter the binary asks for
+ * @bprm: binary that is being executed
+ * @path: the interpreter path named in the binary's PT_INTERP
+ *
+ * A binfmt_misc loader entry substitutes for the interpreter the binary
+ * names. Hand out the stashed substitute if there is one and open @path
+ * if there is not. The caller owns the reference either way and releases
+ * it like any other open_exec() one.
+ *
+ * Return: the interpreter on success, an ERR_PTR on failure
+ */
+struct file *bprm_open_interpreter(struct linux_binprm *bprm, const char *path)
+{
+ if (bprm->loader)
+ return no_free_ptr(bprm->loader);
+ return open_exec(path);
+}
+
+/**
+ * bprm_drop_loader - discard a PT_INTERP substitute that does not apply
+ * @bprm: binary that is being executed
+ *
+ * A binary without PT_INTERP has nothing to substitute for, so drop the
+ * override and let the binary load natively rather than have
+ * begin_new_exec() refuse it. A no-op once bprm_open_interpreter() took
+ * the substitute.
+ */
+void bprm_drop_loader(struct linux_binprm *bprm)
+{
+ do_close_execat(no_free_ptr(bprm->loader));
+}
+
static void free_bprm(struct linux_binprm *bprm)
{
if (bprm->mm) {
@@ -1413,8 +1470,9 @@ static void free_bprm(struct linux_binprm *bprm)
if (bprm->old_mm)
exec_mm_put_old(bprm->old_mm);
do_close_execat(bprm->file);
- if (bprm->executable)
- fput(bprm->executable);
+ /* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */
+ bprm_drop_loader(bprm);
+ do_close_execat(bprm->executable);
/* If a binfmt changed the interp, free it. */
if (bprm->interp != bprm->filename)
kfree(bprm->interp);
@@ -1731,19 +1789,23 @@ static int exec_binprm(struct linux_binprm *bprm)
if (!bprm->interpreter)
break;
+ /* A stashed PT_INTERP substitute belonged to the replaced file. */
+ bprm_drop_loader(bprm);
+
exec = bprm->file;
bprm->file = bprm->interpreter;
bprm->interpreter = NULL;
- exe_file_allow_write_access(exec);
if (unlikely(bprm->have_execfd)) {
if (bprm->executable) {
- fput(exec);
+ do_close_execat(exec);
return -ENOEXEC;
}
+ /* Kept for AT_EXECFD; the write denial rides along until hand-over. */
bprm->executable = exec;
- } else
- fput(exec);
+ } else {
+ do_close_execat(exec);
+ }
}
audit_bprm(bprm);
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 4fd4ec52b8c0..8d2ae6e33cbf 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -299,7 +299,13 @@ int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu)
if (ret)
return ret;
- return exfat_zeroed_cluster(inode, clu->dir);
+ ret = exfat_zeroed_cluster(inode, clu->dir);
+ if (ret) {
+ exfat_free_cluster(inode, clu);
+ return ret;
+ }
+
+ return 0;
}
int exfat_calc_num_entries(struct exfat_uni_name *p_uniname)
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index cd9c9eca58f8..a4dc83b5949c 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -471,6 +471,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
struct exfat_entry_set_cache es;
int clu_size = 0;
unsigned int start_clu = EXFAT_FREE_CLUSTER;
+ bool dir_allocated = false;
ret = exfat_resolve_path(inode, path, &uniname);
if (ret)
@@ -497,6 +498,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
}
start_clu = clu.dir;
clu_size = sbi->cluster_size;
+ dir_allocated = true;
}
/* update the directory entry */
@@ -507,8 +509,21 @@ static int exfat_add_entry(struct inode *inode, const char *path,
exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
- if (ret)
+ if (ret) {
+ int cleanup_ret;
+
+ cleanup_ret = exfat_get_dentry_set(&es, sb, &info->dir,
+ dentry, ES_ALL_ENTRIES);
+ if (!cleanup_ret) {
+ exfat_remove_entries(inode, &es, ES_IDX_FILE, false);
+ cleanup_ret = exfat_put_dentry_set(&es,
+ IS_DIRSYNC(inode));
+ }
+
+ if (!cleanup_ret && dir_allocated)
+ exfat_free_cluster(inode, &clu);
goto out;
+ }
info->entry = dentry;
info->flags = ALLOC_NO_FAT_CHAIN;
@@ -1116,11 +1131,6 @@ static int exfat_move_file(struct inode *parent_inode,
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
&mov_es, num_extra_entries);
- exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
-
- ei->dir = newdir;
- ei->entry = newentry;
-
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
if (ret) {
/* Best-effort delete to avoid duplicate entries */
@@ -1134,6 +1144,11 @@ static int exfat_move_file(struct inode *parent_inode,
goto put_mov_es;
}
+ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
+
+ ei->dir = newdir;
+ ei->entry = newentry;
+
return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es:
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index 278d4be8ecbe..e17bbc7598c1 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -734,6 +734,6 @@ const struct file_operations ext2_dir_operations = {
#ifdef CONFIG_COMPAT
.compat_ioctl = ext2_compat_ioctl,
#endif
- .fsync = ext2_fsync,
+ .fsync = simple_fsync,
.setlease = generic_setlease,
};
diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h
index 79f7b395258c..5642451bf191 100644
--- a/fs/ext2/ext2.h
+++ b/fs/ext2/ext2.h
@@ -735,6 +735,7 @@ extern unsigned long ext2_count_free (struct buffer_head *, unsigned);
/* inode.c */
extern struct inode *ext2_iget (struct super_block *, unsigned long);
extern int ext2_write_inode (struct inode *, struct writeback_control *);
+extern int ext2_sync_inode_metadata(struct inode *, struct writeback_control *);
extern void ext2_evict_inode(struct inode *);
void ext2_write_failed(struct address_space *mapping, loff_t to);
extern int ext2_get_block(struct inode *, sector_t, struct buffer_head *, int);
@@ -772,8 +773,6 @@ extern void ext2_sync_super(struct super_block *sb, struct ext2_super_block *es,
extern const struct file_operations ext2_dir_operations;
/* file.c */
-extern int ext2_fsync(struct file *file, loff_t start, loff_t end,
- int datasync);
extern const struct inode_operations ext2_file_inode_operations;
extern const struct file_operations ext2_file_operations;
diff --git a/fs/ext2/file.c b/fs/ext2/file.c
index 8dca9ec4cacd..b9020df7d89e 100644
--- a/fs/ext2/file.c
+++ b/fs/ext2/file.c
@@ -47,21 +47,6 @@ static int ext2_release_file (struct inode * inode, struct file * filp)
return 0;
}
-int ext2_fsync(struct file *file, loff_t start, loff_t end, int datasync)
-{
- int ret;
- struct inode *inode = file->f_mapping->host;
- struct super_block *sb = inode->i_sb;
-
- ret = mmb_fsync(file, &EXT2_I(inode)->i_metadata_bhs,
- start, end, datasync);
- if (ret == -EIO)
- /* We don't really know where the IO error happened... */
- ext2_error(sb, __func__,
- "detected IO error when writing metadata buffers");
- return ret;
-}
-
static ssize_t ext2_dio_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
@@ -213,7 +198,7 @@ const struct file_operations ext2_file_operations = {
.mmap_prepare = generic_file_mmap_prepare,
.open = ext2_file_open,
.release = ext2_release_file,
- .fsync = ext2_fsync,
+ .fsync = simple_fsync,
.get_unmapped_area = thp_get_unmapped_area,
.splice_read = filemap_splice_read,
.splice_write = iter_file_splice_write,
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 29808629cce5..b5c958db9ecf 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -39,8 +39,6 @@
#include "acl.h"
#include "xattr.h"
-static int __ext2_write_inode(struct inode *inode, int do_sync);
-
/*
* Test whether an inode is a fast symlink.
*/
@@ -87,7 +85,7 @@ void ext2_evict_inode(struct inode * inode)
/* set dtime */
EXT2_I(inode)->i_dtime = ktime_get_real_seconds();
mark_inode_dirty(inode);
- __ext2_write_inode(inode, inode_needs_sync(inode));
+ sync_inode_metadata(inode, inode_needs_sync(inode));
/* truncate to 0 */
inode->i_size = 0;
if (inode->i_blocks)
@@ -1258,12 +1256,9 @@ static int ext2_setsize(struct inode *inode, loff_t newsize)
filemap_invalidate_unlock(inode->i_mapping);
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
- if (inode_needs_sync(inode)) {
- mmb_sync(&EXT2_I(inode)->i_metadata_bhs);
+ mark_inode_dirty(inode);
+ if (inode_needs_sync(inode))
sync_inode_metadata(inode, 1);
- } else {
- mark_inode_dirty(inode);
- }
return 0;
}
@@ -1469,7 +1464,7 @@ bad_inode:
return ERR_PTR(ret);
}
-static int __ext2_write_inode(struct inode *inode, int do_sync)
+int ext2_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct ext2_inode_info *ei = EXT2_I(inode);
struct super_block *sb = inode->i_sb;
@@ -1560,22 +1555,38 @@ static int __ext2_write_inode(struct inode *inode, int do_sync)
} else for (n = 0; n < EXT2_N_BLOCKS; n++)
raw_inode->i_block[n] = ei->i_data[n];
mark_buffer_dirty(bh);
- if (do_sync) {
- sync_dirty_buffer(bh);
- if (buffer_req(bh) && !buffer_uptodate(bh)) {
- printk ("IO error syncing ext2 inode [%s:%08lx]\n",
- sb->s_id, (unsigned long) ino);
- err = -EIO;
- }
- }
ei->i_state &= ~EXT2_STATE_NEW;
brelse (bh);
+ set_inode_metadata_writeback(inode);
return err;
}
-int ext2_write_inode(struct inode *inode, struct writeback_control *wbc)
+int ext2_sync_inode_metadata(struct inode *inode, struct writeback_control *wbc)
{
- return __ext2_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
+ struct buffer_head *bh;
+ struct ext2_inode *raw_inode = ext2_get_inode(inode->i_sb, inode->i_ino,
+ &bh);
+ int err = 0;
+
+ if (IS_ERR(raw_inode))
+ return -EIO;
+ err = mmb_sync(&EXT2_I(inode)->i_metadata_bhs);
+ if (err) {
+ ext2_error(inode->i_sb, __func__,
+ "Error syncing inode metadata ino=%lu\n",
+ (unsigned long)inode->i_ino);
+ goto out;
+ }
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ ext2_error(inode->i_sb, __func__,
+ "IO error syncing inode %lu\n",
+ (unsigned long)inode->i_ino);
+ err = -EIO;
+ }
+out:
+ brelse(bh);
+ return err;
}
int ext2_getattr(struct mnt_idmap *idmap, const struct path *path,
diff --git a/fs/ext2/super.c b/fs/ext2/super.c
index 3999f8f3b156..a40f530872a4 100644
--- a/fs/ext2/super.c
+++ b/fs/ext2/super.c
@@ -362,6 +362,7 @@ static const struct super_operations ext2_sops = {
.alloc_inode = ext2_alloc_inode,
.free_inode = ext2_free_in_core_inode,
.write_inode = ext2_write_inode,
+ .sync_inode_metadata = ext2_sync_inode_metadata,
.evict_inode = ext2_evict_inode,
.put_super = ext2_put_super,
.sync_fs = ext2_sync_fs,
diff --git a/fs/ext2/xattr.c b/fs/ext2/xattr.c
index e55d16abf422..9b68c490ab26 100644
--- a/fs/ext2/xattr.c
+++ b/fs/ext2/xattr.c
@@ -777,20 +777,16 @@ ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh,
/* Update the inode. */
EXT2_I(inode)->i_file_acl = new_bh ? new_bh->b_blocknr : 0;
inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
if (IS_SYNC(inode)) {
error = sync_inode_metadata(inode, 1);
- /* In case sync failed due to ENOSPC the inode was actually
- * written (only some dirty data were not) so we just proceed
- * as if nothing happened and cleanup the unused block */
- if (error && error != -ENOSPC) {
- if (new_bh && new_bh != old_bh) {
- dquot_free_block_nodirty(inode, 1);
- mark_inode_dirty(inode);
- }
+ /*
+ * Inode writeout failed. Backing everything out is complex so
+ * let's just leave it for e2fsck to cleanup the mess.
+ */
+ if (error)
goto cleanup;
- }
- } else
- mark_inode_dirty(inode);
+ }
error = 0;
if (old_bh && old_bh != new_bh) {
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 4e3b3165ee8f..147e8dd466ea 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1157,7 +1157,7 @@ struct ext4_inode_info {
struct rw_semaphore i_data_sem;
struct inode vfs_inode;
struct jbd2_inode *jinode;
- struct mapping_metadata_bhs i_metadata_bhs;
+ struct mapping_metadata_bhs *i_metadata_bhs;
/*
* File creation time. Its function is same as that of
@@ -2137,6 +2137,17 @@ static inline bool ext4_inode_orphan_tracked(struct inode *inode)
!list_empty(&EXT4_I(inode)->i_orphan);
}
+static inline struct mapping_metadata_bhs *ext4_i_metadata_bhs(
+ struct inode *inode)
+{
+ /*
+ * i_metadata_bhs is set in ext4_inode_attach_mmb() using cmpxchg().
+ * We use READ_ONCE when accessing i_metadata_bhs to make sure we get
+ * consistent view for all accesses.
+ */
+ return READ_ONCE(EXT4_I(inode)->i_metadata_bhs);
+}
+
/*
* Codes for operating systems
*/
@@ -3167,6 +3178,7 @@ extern struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
__ext4_iget((sb), (ino), (flags), __func__, __LINE__)
extern int ext4_write_inode(struct inode *, struct writeback_control *);
+extern int ext4_sync_inode_metadata(struct inode *, struct writeback_control *);
extern int ext4_setattr(struct mnt_idmap *, struct dentry *,
struct iattr *);
extern u32 ext4_dio_alignment(struct inode *inode);
diff --git a/fs/ext4/ext4_jbd2.c b/fs/ext4/ext4_jbd2.c
index b4dacd1a89e7..53ddedb52a6f 100644
--- a/fs/ext4/ext4_jbd2.c
+++ b/fs/ext4/ext4_jbd2.c
@@ -358,6 +358,21 @@ int __ext4_journal_get_create_access(const char *where, unsigned int line,
return 0;
}
+static void ext4_inode_attach_mmb(struct inode *inode)
+{
+ struct mapping_metadata_bhs *mmb;
+
+ /*
+ * It's difficult to handle failure when marking buffer dirty without
+ * leaving filesystem corrupted
+ */
+ mmb = kmalloc_obj(*mmb, GFP_NOFS | __GFP_NOFAIL | __GFP_ACCOUNT);
+ mmb_init(mmb, &inode->i_data);
+ /* Someone swapped another mmb before us? */
+ if (cmpxchg(&EXT4_I(inode)->i_metadata_bhs, NULL, mmb))
+ kfree(mmb);
+}
+
int __ext4_handle_dirty_metadata(const char *where, unsigned int line,
handle_t *handle, struct inode *inode,
struct buffer_head *bh)
@@ -397,11 +412,13 @@ int __ext4_handle_dirty_metadata(const char *where, unsigned int line,
err);
}
} else {
- if (inode)
- mmb_mark_buffer_dirty(bh,
- &EXT4_I(inode)->i_metadata_bhs);
- else
+ if (inode) {
+ if (!ext4_i_metadata_bhs(inode))
+ ext4_inode_attach_mmb(inode);
+ mmb_mark_buffer_dirty(bh, ext4_i_metadata_bhs(inode));
+ } else {
mark_buffer_dirty(bh);
+ }
if (inode && inode_needs_sync(inode)) {
sync_dirty_buffer(bh);
if (buffer_req(bh) && !buffer_uptodate(bh)) {
diff --git a/fs/ext4/fsync.c b/fs/ext4/fsync.c
index 924726dcc85f..2999c2cc8fcf 100644
--- a/fs/ext4/fsync.c
+++ b/fs/ext4/fsync.c
@@ -68,9 +68,6 @@ static int ext4_sync_parent(struct inode *inode)
* through ext4_evict_inode()) and so we are safe to flush
* metadata blocks and the inode.
*/
- ret = mmb_sync(&EXT4_I(inode)->i_metadata_bhs);
- if (ret)
- break;
ret = sync_inode_metadata(inode, 1);
if (ret)
break;
@@ -83,22 +80,11 @@ static int ext4_fsync_nojournal(struct file *file, loff_t start, loff_t end,
int datasync, bool *needs_barrier)
{
struct inode *inode = file->f_inode;
- struct writeback_control wbc = {
- .sync_mode = WB_SYNC_ALL,
- .nr_to_write = 0,
- };
int ret;
- ret = mmb_fsync_noflush(file, &EXT4_I(inode)->i_metadata_bhs,
- start, end, datasync);
+ ret = sync_inode_metadata(inode, 1);
if (ret)
return ret;
-
- /* Force writeout of inode table buffer to disk */
- ret = ext4_write_inode(inode, &wbc);
- if (ret)
- return ret;
-
ret = ext4_sync_parent(inode);
if (test_opt(inode->i_sb, BARRIER))
@@ -156,6 +142,10 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
if (sb_rdonly(inode->i_sb))
goto out;
+ ret = file_write_and_wait_range(file, start, end);
+ if (ret)
+ goto out;
+
if (!EXT4_SB(inode->i_sb)->s_journal) {
ret = ext4_fsync_nojournal(file, start, end, datasync,
&needs_barrier);
@@ -164,10 +154,6 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
goto out;
}
- ret = file_write_and_wait_range(file, start, end);
- if (ret)
- goto out;
-
/*
* The caller's filemap_fdatawrite()/wait will sync the data.
* Metadata is in the journal, we wait for proper transaction to
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 853efadc944e..5fda0e80c613 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -185,6 +185,8 @@ void ext4_evict_inode(struct inode *inode)
if (EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)
ext4_evict_ea_inode(inode);
if (inode->i_nlink) {
+ struct mapping_metadata_bhs *mmb;
+
/*
* If there's dirty page will lead to data loss, user
* could see stale data.
@@ -194,9 +196,9 @@ void ext4_evict_inode(struct inode *inode)
ext4_warning_inode(inode, "data will be lost");
truncate_inode_pages_final(&inode->i_data);
- /* Avoid mballoc special inode which has no proper iops */
- if (!EXT4_SB(inode->i_sb)->s_journal)
- mmb_sync(&EXT4_I(inode)->i_metadata_bhs);
+ mmb = ext4_i_metadata_bhs(inode);
+ if (mmb)
+ mmb_sync(mmb);
goto no_delete;
}
@@ -3439,6 +3441,7 @@ static bool ext4_release_folio(struct folio *folio, gfp_t wait)
static bool ext4_inode_datasync_dirty(struct inode *inode)
{
journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
+ struct mapping_metadata_bhs *mmb;
if (journal) {
if (jbd2_transaction_committed(journal,
@@ -3449,8 +3452,9 @@ static bool ext4_inode_datasync_dirty(struct inode *inode)
return true;
}
+ mmb = ext4_i_metadata_bhs(inode);
/* Any metadata buffers to write? */
- if (mmb_has_buffers(&EXT4_I(inode)->i_metadata_bhs))
+ if (mmb && mmb_has_buffers(mmb))
return true;
return inode_state_read_once(inode) & I_DIRTY_DATASYNC;
}
@@ -5846,6 +5850,10 @@ out_brelse:
* ext4_mark_inode_dirty(). This is a correctness thing for WB_SYNC_ALL
* writeback.
*
+ * For nojournal mode all the work is done in ext4_sync_inode_metadata()
+ * because inode content is already copied into raw inode buffer and inode
+ * is marked with I_METADATA_WRITEBACK.
+ *
* Note that we are absolutely dependent upon all inode dirtiers doing the
* right thing: they *must* call mark_inode_dirty() after dirtying info in
* which we are interested.
@@ -5871,42 +5879,54 @@ int ext4_write_inode(struct inode *inode, struct writeback_control *wbc)
if (unlikely(err))
return err;
- if (EXT4_SB(inode->i_sb)->s_journal) {
- if (ext4_journal_current_handle()) {
- ext4_debug("called recursively, non-PF_MEMALLOC!\n");
- dump_stack();
- return -EIO;
- }
+ if (!EXT4_SB(inode->i_sb)->s_journal)
+ return 0;
- /*
- * No need to force transaction in WB_SYNC_NONE mode. Also
- * ext4_sync_fs() will force the commit after everything is
- * written.
- */
- if (wbc->sync_mode != WB_SYNC_ALL || wbc->for_sync)
- return 0;
+ if (ext4_journal_current_handle()) {
+ ext4_debug("called recursively, non-PF_MEMALLOC!\n");
+ dump_stack();
+ return -EIO;
+ }
+
+ /*
+ * No need to force transaction in WB_SYNC_NONE mode. Also
+ * ext4_sync_fs() will force the commit after everything is
+ * written.
+ */
+ if (wbc->sync_mode != WB_SYNC_ALL || wbc->for_sync)
+ return 0;
- err = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal,
+ return ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal,
EXT4_I(inode)->i_sync_tid);
- } else {
- struct ext4_iloc iloc;
+}
- err = __ext4_get_inode_loc_noinmem(inode, &iloc);
+int ext4_sync_inode_metadata(struct inode *inode, struct writeback_control *wbc)
+{
+ struct ext4_iloc iloc;
+ struct mapping_metadata_bhs *mmb;
+ int err;
+
+ /* We should only get here in nojournal mode */
+ if (WARN_ON_ONCE(EXT4_SB(inode->i_sb)->s_journal))
+ return -EFSCORRUPTED;
+
+ err = __ext4_get_inode_loc_noinmem(inode, &iloc);
+ if (err)
+ return err;
+ mmb = READ_ONCE(EXT4_I(inode)->i_metadata_bhs);
+ if (mmb) {
+ err = mmb_sync(mmb);
if (err)
- return err;
- /*
- * sync(2) will flush the whole buffer cache. No need to do
- * it here separately for each inode.
- */
- if (wbc->sync_mode == WB_SYNC_ALL && !wbc->for_sync)
- sync_dirty_buffer(iloc.bh);
- if (buffer_req(iloc.bh) && !buffer_uptodate(iloc.bh)) {
- ext4_error_inode_block(inode, iloc.bh->b_blocknr, EIO,
- "IO error syncing inode");
- err = -EIO;
- }
- brelse(iloc.bh);
+ goto out;
}
+ sync_dirty_buffer(iloc.bh);
+ if (buffer_write_io_error(iloc.bh)) {
+ ext4_error_inode_block(inode, iloc.bh->b_blocknr, EIO,
+ "IO error syncing inode");
+ err = -EIO;
+ }
+out:
+ brelse(iloc.bh);
return err;
}
@@ -6447,6 +6467,20 @@ int ext4_mark_iloc_dirty(handle_t *handle,
/* ext4_do_update_inode() does jbd2_journal_dirty_metadata */
err = ext4_do_update_inode(handle, inode, iloc);
put_bh(iloc->bh);
+ /*
+ * Mark that there's metadata writeout pending for the inode so that it
+ * gets properly flushed on fsync(2) and similar.
+ */
+ if (!EXT4_SB(inode->i_sb)->s_journal) {
+ /*
+ * Inode didn't need to go through dirtying, make sure it is
+ * attached to wb so that writeback can handle it.
+ */
+ spin_lock(&inode->i_lock);
+ inode_attach_wb(inode, NULL);
+ spin_unlock(&inode->i_lock);
+ set_inode_metadata_writeback(inode);
+ }
return err;
}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 8e7c93a5e458..5ec7ddd9d64a 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1439,7 +1439,7 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work);
ext4_fc_init_inode(&ei->vfs_inode);
spin_lock_init(&ei->i_fc_lock);
- mmb_init(&ei->i_metadata_bhs, &ei->vfs_inode.i_data);
+ ei->i_metadata_bhs = NULL;
#ifdef CONFIG_LOCKDEP
lockdep_set_subclass(&ei->i_data_sem, I_DATA_SEM_NORMAL);
#endif
@@ -1460,6 +1460,7 @@ static int ext4_drop_inode(struct inode *inode)
static void ext4_free_in_core_inode(struct inode *inode)
{
fscrypt_free_inode(inode);
+ kfree(ext4_i_metadata_bhs(inode));
if (!list_empty(&(EXT4_I(inode)->i_fc_list))) {
pr_warn("%s: inode %llu still in fc list",
__func__, inode->i_ino);
@@ -1537,9 +1538,11 @@ static void destroy_inodecache(void)
void ext4_clear_inode(struct inode *inode)
{
+ struct mapping_metadata_bhs *mmb = ext4_i_metadata_bhs(inode);
+
ext4_fc_del(inode);
- if (!EXT4_SB(inode->i_sb)->s_journal)
- mmb_invalidate(&EXT4_I(inode)->i_metadata_bhs);
+ if (mmb)
+ mmb_invalidate(mmb);
clear_inode(inode);
ext4_discard_preallocations(inode);
/*
@@ -1613,9 +1616,13 @@ static int ext4_nfs_commit_metadata(struct inode *inode)
struct writeback_control wbc = {
.sync_mode = WB_SYNC_ALL
};
+ int ret;
trace_ext4_nfs_commit_metadata(inode);
- return ext4_write_inode(inode, &wbc);
+ ret = ext4_write_inode(inode, &wbc);
+ if (!ret && inode_state_read_once(inode) & I_METADATA_WRITEBACK)
+ ret = ext4_sync_inode_metadata(inode, &wbc);
+ return ret;
}
#ifdef CONFIG_QUOTA
@@ -1672,6 +1679,7 @@ static const struct super_operations ext4_sops = {
.free_inode = ext4_free_in_core_inode,
.destroy_inode = ext4_destroy_inode,
.write_inode = ext4_write_inode,
+ .sync_inode_metadata = ext4_sync_inode_metadata,
.dirty_inode = ext4_dirty_inode,
.drop_inode = ext4_drop_inode,
.evict_inode = ext4_evict_inode,
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index a9563f7fcd88..0562ad211351 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -320,6 +320,7 @@ start_find_bucket:
de = find_in_block(dir, dentry_folio, fname, &max_slots, use_hash);
if (IS_ERR(de)) {
+ f2fs_folio_put(dentry_folio, false);
*res_folio = ERR_CAST(de);
de = NULL;
break;
diff --git a/fs/failfs.c b/fs/failfs.c
new file mode 100644
index 000000000000..66a36da3d236
--- /dev/null
+++ b/fs/failfs.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Christian Brauner <brauner@kernel.org> */
+#include <linux/fs.h>
+#include <linux/fs/super_types.h>
+#include <linux/fs_context.h>
+#include <linux/fs_struct.h>
+#include <linux/magic.h>
+#include <linux/mount.h>
+
+#include "internal.h"
+
+static struct path failfs_root_path = {};
+
+void failfs_get_root(struct path *path)
+{
+ *path = failfs_root_path;
+ path_get(path);
+}
+
+bool failfs_mnt(const struct vfsmount *mnt)
+{
+ return mnt->mnt_sb == failfs_root_path.mnt->mnt_sb;
+}
+
+static int failfs_permission(struct mnt_idmap *idmap, struct inode *inode,
+ int mask)
+{
+ return -EOPNOTSUPP;
+}
+
+static struct dentry *failfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ /* Unreachable: ->permission() already failed the walk. */
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static int failfs_getattr(struct mnt_idmap *idmap, const struct path *path,
+ struct kstat *stat, u32 request_mask,
+ unsigned int query_flags)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct inode_operations failfs_dir_inode_operations = {
+ .permission = failfs_permission,
+ .lookup = failfs_lookup,
+ .getattr = failfs_getattr,
+};
+
+static const struct file_operations failfs_dir_operations = {};
+
+static int failfs_d_weak_revalidate(struct dentry *dentry, unsigned int flags)
+{
+ /*
+ * The root is only ever reached as a path-walk terminal by jumping
+ * to it: as "/" when it is the caller's root, or through a
+ * /proc/<pid>/{root,cwd} magic link. ->permission() already fails
+ * every walk of a component, but a jump lands on the root without
+ * one. Refuse here too so the root cannot be pinned by an O_PATH
+ * open or encoded into a file handle.
+ */
+ return -EOPNOTSUPP;
+}
+
+static char *failfs_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+ return dynamic_dname(buffer, buflen, "failfs:/");
+}
+
+static const struct dentry_operations failfs_dentry_operations = {
+ .d_dname = failfs_dname,
+ .d_weak_revalidate = failfs_d_weak_revalidate,
+};
+
+static int failfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct super_operations failfs_super_operations = {
+ .statfs = failfs_statfs,
+};
+
+static int failfs_fill_super(struct super_block *s, struct fs_context *fc)
+{
+ struct inode *inode;
+
+ s->s_maxbytes = MAX_LFS_FILESIZE;
+ s->s_blocksize = PAGE_SIZE;
+ s->s_blocksize_bits = PAGE_SHIFT;
+ s->s_magic = FAIL_FS_MAGIC;
+ s->s_op = &failfs_super_operations;
+ s->s_export_op = NULL;
+ s->s_xattr = NULL;
+ s->s_time_gran = 1;
+ s->s_d_flags = 0;
+
+ inode = new_inode(s);
+ if (!inode)
+ return -ENOMEM;
+
+ /* failfs supports no operations... */
+ inode->i_mode = S_IFDIR;
+ set_nlink(inode, 2);
+ inode->i_op = &failfs_dir_inode_operations;
+ inode->i_fop = &failfs_dir_operations;
+ simple_inode_init_ts(inode);
+ inode->i_ino = 1;
+ /* ... and is immutable. */
+ inode->i_flags |= S_IMMUTABLE;
+
+ set_default_d_op(s, &failfs_dentry_operations);
+ s->s_root = d_make_root(inode);
+ if (!s->s_root)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int failfs_get_tree(struct fs_context *fc)
+{
+ return get_tree_single(fc, failfs_fill_super);
+}
+
+static const struct fs_context_operations failfs_context_ops = {
+ .get_tree = failfs_get_tree,
+};
+
+static int failfs_init_fs_context(struct fs_context *fc)
+{
+ fc->ops = &failfs_context_ops;
+ fc->global = true;
+ fc->sb_flags |= SB_NOUSER;
+ fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
+ return 0;
+}
+
+int failfs_current_chdir(void)
+{
+ struct path path;
+
+ failfs_get_root(&path);
+ set_fs_pwd(current->fs, &path);
+ path_put(&path);
+ return 0;
+}
+
+static struct file_system_type failfs_fs_type = {
+ .name = "failfs",
+ .init_fs_context = failfs_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
+void __init failfs_init(void)
+{
+ struct vfsmount *mnt;
+
+ /* A single instance that is member of no mount namespace. */
+ mnt = kern_mount(&failfs_fs_type);
+ if (IS_ERR(mnt))
+ panic("VFS: Failed to create failfs");
+
+ failfs_root_path.mnt = mnt;
+ failfs_root_path.dentry = mnt->mnt_root;
+}
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index c6cca5d00ffd..35bdb62944a2 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -1109,10 +1109,10 @@ int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo)
}
fat_truncate_time(dir, NULL, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
+ err = 0;
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
return 0;
}
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 2772675bd35a..61338413d9f3 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -421,7 +421,6 @@ extern void fat_detach(struct inode *inode);
extern struct inode *fat_iget(struct super_block *sb, loff_t i_pos);
extern struct inode *fat_build_inode(struct super_block *sb,
struct msdos_dir_entry *de, loff_t i_pos);
-extern int fat_sync_inode(struct inode *inode);
extern int fat_fill_super(struct super_block *sb, struct fs_context *fc,
void (*setup)(struct super_block *));
extern int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de);
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 37e7049b4c8c..1c835ca5f21a 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -190,8 +190,7 @@ int fat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
struct inode *fat_inode = MSDOS_SB(inode->i_sb)->fat_inode;
int err;
- err = mmb_fsync_noflush(filp, &MSDOS_I(inode)->i_metadata_bhs,
- start, end, datasync);
+ err = simple_fsync_noflush(filp, start, end, datasync);
if (err)
return err;
@@ -332,15 +331,15 @@ static int fat_free(struct inode *inode, int skip)
}
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(inode);
if (wait) {
- err = fat_sync_inode(inode);
+ err = sync_inode_metadata(inode, 1);
if (err) {
MSDOS_I(inode)->i_start = i_start;
MSDOS_I(inode)->i_logstart = i_logstart;
return err;
}
- } else
- mark_inode_dirty(inode);
+ }
/* Write a new EOF, and get the remaining cluster chain for freeing. */
if (skip) {
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index d1e6c9b2f9df..f775a004cae1 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -623,7 +623,41 @@ out:
EXPORT_SYMBOL_GPL(fat_build_inode);
-static int __fat_write_inode(struct inode *inode, int wait);
+static int __fat_write_inode(struct inode *inode);
+
+static int fat_sync_inode_metadata(struct inode *inode,
+ struct writeback_control *wbc)
+{
+ struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+ struct buffer_head *bh;
+ loff_t i_pos;
+ sector_t blocknr;
+ int offset;
+
+ /* The root directory has no directory entry of its own. */
+ if (inode->i_ino == MSDOS_ROOT_INO)
+ goto sync_bhs;
+ i_pos = fat_i_pos_read(sbi, inode);
+ if (!i_pos)
+ goto sync_bhs;
+
+ fat_get_blknr_offset(sbi, i_pos, &blocknr, &offset);
+ bh = sb_find_get_block_nonatomic(inode->i_sb, blocknr);
+ /*
+ * Buffer present? We leave buffer_dirty check for sync_dirty_buffer()
+ * for proper synchronization with ongoing IO.
+ */
+ if (bh && buffer_uptodate(bh)) {
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ brelse(bh);
+ return -EIO;
+ }
+ }
+ brelse(bh);
+sync_bhs:
+ return mmb_sync(&MSDOS_I(inode)->i_metadata_bhs);
+}
static void fat_free_eofblocks(struct inode *inode)
{
@@ -640,7 +674,7 @@ static void fat_free_eofblocks(struct inode *inode)
* any corruption on the next access to the cluster
* chain for the file.
*/
- err = __fat_write_inode(inode, inode_needs_sync(inode));
+ err = sync_inode_metadata(inode, inode_needs_sync(inode));
if (err) {
fat_msg(inode->i_sb, KERN_WARNING, "Failed to "
"update on disk inode for unused "
@@ -854,7 +888,7 @@ static int fat_statfs(struct dentry *dentry, struct kstatfs *buf)
return 0;
}
-static int __fat_write_inode(struct inode *inode, int wait)
+static int __fat_write_inode(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
@@ -863,10 +897,13 @@ static int __fat_write_inode(struct inode *inode, int wait)
struct timespec64 mtime;
loff_t i_pos;
sector_t blocknr;
- int err, offset;
+ int offset;
- if (inode->i_ino == MSDOS_ROOT_INO)
+ if (inode->i_ino == MSDOS_ROOT_INO) {
+ /* No entry to update but the metadata bh list may need syncing. */
+ set_inode_metadata_writeback(inode);
return 0;
+ }
retry:
i_pos = fat_i_pos_read(sbi, inode);
@@ -907,11 +944,9 @@ retry:
}
spin_unlock(&sbi->inode_hash_lock);
mark_buffer_dirty(bh);
- err = 0;
- if (wait)
- err = sync_dirty_buffer(bh);
brelse(bh);
- return err;
+ set_inode_metadata_writeback(inode);
+ return 0;
}
static int fat_write_inode(struct inode *inode, struct writeback_control *wbc)
@@ -925,23 +960,17 @@ static int fat_write_inode(struct inode *inode, struct writeback_control *wbc)
err = fat_clusters_flush(sb);
mutex_unlock(&MSDOS_SB(sb)->s_lock);
} else
- err = __fat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
+ err = __fat_write_inode(inode);
return err;
}
-int fat_sync_inode(struct inode *inode)
-{
- return __fat_write_inode(inode, 1);
-}
-
-EXPORT_SYMBOL_GPL(fat_sync_inode);
-
static int fat_show_options(struct seq_file *m, struct dentry *root);
static const struct super_operations fat_sops = {
.alloc_inode = fat_alloc_inode,
.free_inode = fat_free_inode,
.write_inode = fat_write_inode,
+ .sync_inode_metadata = fat_sync_inode_metadata,
.evict_inode = fat_evict_inode,
.put_super = fat_put_super,
.statfs = fat_statfs,
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 2a0fea26a99a..e79762cf1975 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -150,16 +150,17 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
} else {
MSDOS_I(inode)->i_start = new_dclus;
MSDOS_I(inode)->i_logstart = new_dclus;
+ mark_inode_dirty(inode);
/*
* Since generic_write_sync() synchronizes regular files later,
* we sync here only directories.
*/
if (S_ISDIR(inode->i_mode) && IS_DIRSYNC(inode)) {
- ret = fat_sync_inode(inode);
+ ret = sync_inode_metadata(inode, 1);
if (ret)
return ret;
- } else
- mark_inode_dirty(inode);
+ }
+
}
if (new_fclus != (inode->i_blocks >> (sbi->cluster_bits - 9))) {
fat_fs_error_ratelimit(
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index ee6824c5d136..d46d1a3851f2 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -255,10 +255,9 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name,
return err;
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
return 0;
}
@@ -476,21 +475,20 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
+ mark_inode_dirty(old_inode);
if (IS_DIRSYNC(old_dir)) {
- err = fat_sync_inode(old_inode);
+ err = sync_inode_metadata(old_inode, 1);
if (err) {
MSDOS_I(old_inode)->i_attrs = old_attrs;
goto out;
}
- } else
- mark_inode_dirty(old_inode);
+ }
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, NULL, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(old_dir);
if (IS_DIRSYNC(old_dir))
- (void)fat_sync_inode(old_dir);
- else
- mark_inode_dirty(old_dir);
+ (void)sync_inode_metadata(old_dir, 1);
goto out;
}
}
@@ -521,12 +519,12 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
+ mark_inode_dirty(old_inode);
if (IS_DIRSYNC(new_dir)) {
- err = fat_sync_inode(old_inode);
+ err = sync_inode_metadata(old_inode, 1);
if (err)
goto error_inode;
- } else
- mark_inode_dirty(old_inode);
+ }
if (update_dotdot) {
fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
@@ -548,10 +546,9 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
goto error_dotdot;
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, &ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(old_dir);
if (IS_DIRSYNC(old_dir))
- (void)fat_sync_inode(old_dir);
- else
- mark_inode_dirty(old_dir);
+ (void)sync_inode_metadata(old_dir, 1);
if (new_inode) {
drop_nlink(new_inode);
@@ -580,8 +577,10 @@ error_inode:
MSDOS_I(old_inode)->i_attrs = old_attrs;
if (new_inode) {
fat_attach(new_inode, new_i_pos);
- if (corrupt)
- corrupt |= fat_sync_inode(new_inode);
+ if (corrupt) {
+ mark_inode_dirty(new_inode);
+ corrupt |= sync_inode_metadata(new_inode, 1);
+ }
} else {
/*
* If new entry was not sharing the data cluster, it
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index 139d3ef4bfae..da3e89c0b16a 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -678,10 +678,9 @@ static int vfat_add_entry(struct inode *dir, const struct qstr *qname,
/* update timestamp */
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
cleanup:
kfree(slots);
return err;
@@ -904,9 +903,9 @@ static int vfat_get_dotdot_de(struct inode *inode, struct buffer_head **bh,
static int vfat_sync_ipos(struct inode *dir, struct inode *inode)
{
- if (IS_DIRSYNC(dir))
- return fat_sync_inode(inode);
mark_inode_dirty(inode);
+ if (IS_DIRSYNC(dir))
+ return sync_inode_metadata(inode, 1);
return 0;
}
@@ -925,10 +924,9 @@ static void vfat_update_dir_metadata(struct inode *dir, struct timespec64 *ts)
{
inode_inc_iversion(dir);
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
}
static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -1024,8 +1022,10 @@ error_inode:
fat_attach(old_inode, old_sinfo.i_pos);
if (new_inode) {
fat_attach(new_inode, new_i_pos);
- if (corrupt)
- corrupt |= fat_sync_inode(new_inode);
+ if (corrupt) {
+ mark_inode_dirty(new_inode);
+ corrupt |= sync_inode_metadata(new_inode, 1);
+ }
} else {
/*
* If new entry was not sharing the data cluster, it
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index fdb8766d275a..71dd618db075 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -299,6 +299,7 @@ void __inode_attach_wb(struct inode *inode, struct folio *folio)
if (unlikely(cmpxchg(&inode->i_wb, NULL, wb)))
wb_put(wb);
}
+EXPORT_SYMBOL_GPL(__inode_attach_wb);
/**
* inode_cgwb_move_to_attached - put the inode onto wb->b_attached list
@@ -1851,6 +1852,22 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
if (ret == 0)
ret = err;
}
+
+ /*
+ * Do we need to wait for inode metadata IO possibly submitted
+ * by previous WB_SYNC_NONE writeback?
+ */
+ if (wbc->sync_mode == WB_SYNC_ALL && !wbc->for_sync &&
+ inode_state_read_once(inode) & I_METADATA_WRITEBACK) {
+ int err;
+
+ spin_lock(&inode->i_lock);
+ inode_state_clear(inode, I_METADATA_WRITEBACK);
+ spin_unlock(&inode->i_lock);
+ err = inode->i_sb->s_op->sync_inode_metadata(inode, wbc);
+ if (ret == 0)
+ ret = err;
+ }
wbc->unpinned_netfs_wb = false;
trace_writeback_single_inode(inode, wbc, nr_to_write);
return ret;
@@ -1892,14 +1909,17 @@ static int writeback_single_inode(struct inode *inode,
/*
* If the inode is already fully clean, then there's nothing to do.
*
- * For data-integrity syncs we also need to check whether any pages are
- * still under writeback, e.g. due to prior WB_SYNC_NONE writeback. If
- * there are any such pages, we'll need to wait for them.
+ * For data-integrity syncs we also need to check whether any folios or
+ * metadata are still under writeback, e.g. due to prior WB_SYNC_NONE
+ * writeback. If there, we'll need to wait for them.
*/
- if (!(inode_state_read(inode) & I_DIRTY_ALL) &&
- (wbc->sync_mode != WB_SYNC_ALL ||
- !mapping_tagged(inode->i_mapping, PAGECACHE_TAG_WRITEBACK)))
- goto out;
+ if (!(inode_state_read(inode) & I_DIRTY_ALL)) {
+ if (wbc->sync_mode != WB_SYNC_ALL)
+ goto out;
+ if (!mapping_tagged(inode->i_mapping, PAGECACHE_TAG_WRITEBACK) &&
+ !(inode_state_read(inode) & I_METADATA_WRITEBACK))
+ goto out;
+ }
inode_state_set(inode, I_SYNC);
wbc_attach_and_unlock_inode(wbc, inode);
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ceada75310b8..da5859e8159d 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1219,8 +1219,7 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia,
struct file *file = iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct fuse_mount *fm = ff->fm;
- unsigned int offset, i;
- bool short_write;
+ unsigned int i;
int err;
for (i = 0; i < ap->num_folios; i++)
@@ -1235,24 +1234,9 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia,
if (!err && ia->write.out.size > count)
err = -EIO;
- short_write = ia->write.out.size < count;
- offset = ap->descs[0].offset;
- count = ia->write.out.size;
for (i = 0; i < ap->num_folios; i++) {
struct folio *folio = ap->folios[i];
- if (err) {
- folio_clear_uptodate(folio);
- } else {
- if (count >= folio_size(folio) - offset)
- count -= folio_size(folio) - offset;
- else {
- if (short_write)
- folio_clear_uptodate(folio);
- count = 0;
- }
- offset = 0;
- }
if (ia->write.folio_locked && (i == ap->num_folios - 1))
folio_unlock(folio);
folio_put(folio);
@@ -1327,7 +1311,7 @@ static ssize_t fuse_fill_write_pages(struct fuse_io_args *ia,
/* If we copied full folio, mark it uptodate */
if (tmp == folio_size(folio))
- folio_mark_uptodate(folio);
+ iomap_folio_mark_uptodate(folio);
if (folio_test_uptodate(folio)) {
folio_unlock(folio);
diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c
index 29578104ae6c..1ba763705d91 100644
--- a/fs/fuse/notify.c
+++ b/fs/fuse/notify.c
@@ -2,6 +2,8 @@
#include "dev.h"
#include "fuse_i.h"
+
+#include <linux/iomap.h>
#include <linux/pagemap.h>
static int fuse_notify_poll(struct fuse_conn *fc, unsigned int size,
@@ -192,7 +194,7 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
if (!folio_test_uptodate(folio) && !err && folio_offset == 0 &&
(nr_bytes == folio_size(folio) || file_size == end)) {
folio_zero_segment(folio, nr_bytes, folio_size(folio));
- folio_mark_uptodate(folio);
+ iomap_folio_mark_uptodate(folio);
}
folio_unlock(folio);
folio_put(folio);
diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c
index b8a144d3a73b..334fee90c27e 100644
--- a/fs/gfs2/glock.c
+++ b/fs/gfs2/glock.c
@@ -66,9 +66,6 @@ static void request_demote(struct gfs2_glock *gl, unsigned int state,
unsigned long delay, bool remote);
static struct dentry *gfs2_root;
-static LIST_HEAD(lru_list);
-static atomic_t lru_count = ATOMIC_INIT(0);
-static DEFINE_SPINLOCK(lru_lock);
#define GFS2_GL_HASH_SHIFT 15
#define GFS2_GL_HASH_SIZE BIT(GFS2_GL_HASH_SHIFT)
@@ -78,6 +75,7 @@ static const struct rhashtable_params ht_parms = {
.key_len = offsetofend(struct lm_lockname, ln_type),
.key_offset = offsetof(struct gfs2_glock, gl_name),
.head_offset = offsetof(struct gfs2_glock, gl_node),
+ .automatic_shrinking = true,
};
static struct rhashtable gl_hash_table;
@@ -157,9 +155,9 @@ void gfs2_glock_free(struct gfs2_glock *gl) {
void gfs2_glock_free_later(struct gfs2_glock *gl) {
struct gfs2_sbd *sdp = glock_sbd(gl);
- spin_lock(&lru_lock);
- list_add(&gl->gl_lru, &sdp->sd_dead_glocks);
- spin_unlock(&lru_lock);
+ spin_lock(&sdp->sd_dead_lock);
+ list_add(&gl->gl_dead, &sdp->sd_dead_glocks);
+ spin_unlock(&sdp->sd_dead_lock);
if (atomic_dec_and_test(&sdp->sd_glock_disposal))
wake_up(&sdp->sd_kill_wait);
}
@@ -171,8 +169,8 @@ static void gfs2_free_dead_glocks(struct gfs2_sbd *sdp)
while(!list_empty(list)) {
struct gfs2_glock *gl;
- gl = list_first_entry(list, struct gfs2_glock, gl_lru);
- list_del_init(&gl->gl_lru);
+ gl = list_first_entry(list, struct gfs2_glock, gl_dead);
+ list_del(&gl->gl_dead);
__gfs2_glock_free(gl);
}
}
@@ -190,30 +188,6 @@ struct gfs2_glock *gfs2_glock_hold(struct gfs2_glock *gl)
return gl;
}
-static void gfs2_glock_add_to_lru(struct gfs2_glock *gl)
-{
- spin_lock(&lru_lock);
- list_move_tail(&gl->gl_lru, &lru_list);
-
- if (!test_bit(GLF_LRU, &gl->gl_flags)) {
- set_bit(GLF_LRU, &gl->gl_flags);
- atomic_inc(&lru_count);
- }
-
- spin_unlock(&lru_lock);
-}
-
-static void gfs2_glock_remove_from_lru(struct gfs2_glock *gl)
-{
- spin_lock(&lru_lock);
- if (test_bit(GLF_LRU, &gl->gl_flags)) {
- list_del_init(&gl->gl_lru);
- atomic_dec(&lru_count);
- clear_bit(GLF_LRU, &gl->gl_flags);
- }
- spin_unlock(&lru_lock);
-}
-
/*
* Enqueue the glock on the work queue. Passes one glock reference on to the
* work queue.
@@ -240,7 +214,6 @@ static void __gfs2_glock_put(struct gfs2_glock *gl)
lockref_mark_dead(&gl->gl_lockref);
spin_unlock(&gl->gl_lockref.lock);
- gfs2_glock_remove_from_lru(gl);
GLOCK_BUG_ON(gl, !list_empty(&gl->gl_holders));
if (mapping) {
truncate_inode_pages_final(mapping);
@@ -257,8 +230,8 @@ static bool __gfs2_glock_put_or_lock(struct gfs2_glock *gl)
return true;
GLOCK_BUG_ON(gl, gl->gl_lockref.count != 1);
if (gl->gl_state != LM_ST_UNLOCKED) {
- gl->gl_lockref.count--;
- gfs2_glock_add_to_lru(gl);
+ request_demote(gl, LM_ST_UNLOCKED, 0, false);
+ gfs2_glock_queue_work(gl, 0);
spin_unlock(&gl->gl_lockref.lock);
return true;
}
@@ -982,16 +955,19 @@ static void delete_work_func(struct work_struct *work)
static void glock_work_func(struct work_struct *work)
{
- unsigned long delay = 0;
struct gfs2_glock *gl = container_of(work, struct gfs2_glock, gl_work.work);
- unsigned int drop_refs = 1;
+ unsigned int drop_refs;
+ unsigned long delay;
spin_lock(&gl->gl_lockref.lock);
+again:
+ drop_refs = 1;
if (test_bit(GLF_HAVE_REPLY, &gl->gl_flags)) {
clear_bit(GLF_HAVE_REPLY, &gl->gl_flags);
finish_xmote(gl, gl->gl_reply);
drop_refs++;
}
+ delay = 0;
if (test_bit(GLF_PENDING_DEMOTE, &gl->gl_flags) &&
gl->gl_state != LM_ST_UNLOCKED &&
gl->gl_demote_state != LM_ST_EXCLUSIVE) {
@@ -1019,11 +995,13 @@ static void glock_work_func(struct work_struct *work)
GLOCK_BUG_ON(gl, gl->gl_lockref.count < drop_refs);
gl->gl_lockref.count -= drop_refs;
if (!gl->gl_lockref.count) {
- if (gl->gl_state == LM_ST_UNLOCKED) {
- __gfs2_glock_put(gl);
- return;
+ if (gl->gl_state != LM_ST_UNLOCKED) {
+ gl->gl_lockref.count++;
+ request_demote(gl, LM_ST_UNLOCKED, 0, false);
+ goto again;
}
- gfs2_glock_add_to_lru(gl);
+ __gfs2_glock_put(gl);
+ return;
}
spin_unlock(&gl->gl_lockref.lock);
}
@@ -1059,8 +1037,6 @@ again:
out:
rcu_read_unlock();
finish_wait(wq, &wait.wait);
- if (gl)
- gfs2_glock_remove_from_lru(gl);
return gl;
}
@@ -1873,125 +1849,6 @@ void gfs2_glock_complete(struct gfs2_glock *gl, int ret)
spin_unlock(&gl->gl_lockref.lock);
}
-static int glock_cmp(void *priv, const struct list_head *a,
- const struct list_head *b)
-{
- struct gfs2_glock *gla, *glb;
-
- gla = list_entry(a, struct gfs2_glock, gl_lru);
- glb = list_entry(b, struct gfs2_glock, gl_lru);
-
- if (glock_number(gla) > glock_number(glb))
- return 1;
- if (glock_number(gla) < glock_number(glb))
- return -1;
-
- return 0;
-}
-
-static bool can_free_glock(struct gfs2_glock *gl)
-{
- struct gfs2_sbd *sdp = glock_sbd(gl);
-
- return !test_bit(GLF_LOCK, &gl->gl_flags) &&
- !gl->gl_lockref.count &&
- (!test_bit(GLF_LFLUSH, &gl->gl_flags) ||
- test_bit(SDF_KILL, &sdp->sd_flags));
-}
-
-/**
- * gfs2_dispose_glock_lru - Demote a list of glocks
- * @list: The list to dispose of
- *
- * Disposing of glocks may involve disk accesses, so that here we sort
- * the glocks by number (i.e. disk location of the inodes) so that if
- * there are any such accesses, they'll be sent in order (mostly).
- *
- * Must be called under the lru_lock, but may drop and retake this
- * lock. While the lru_lock is dropped, entries may vanish from the
- * list, but no new entries will appear on the list (since it is
- * private)
- */
-
-static unsigned long gfs2_dispose_glock_lru(struct list_head *list)
-__releases(&lru_lock)
-__acquires(&lru_lock)
-{
- struct gfs2_glock *gl;
- unsigned long freed = 0;
-
- list_sort(NULL, list, glock_cmp);
-
- while(!list_empty(list)) {
- gl = list_first_entry(list, struct gfs2_glock, gl_lru);
- if (!spin_trylock(&gl->gl_lockref.lock)) {
-add_back_to_lru:
- list_move(&gl->gl_lru, &lru_list);
- continue;
- }
- if (!can_free_glock(gl)) {
- spin_unlock(&gl->gl_lockref.lock);
- goto add_back_to_lru;
- }
- list_del_init(&gl->gl_lru);
- atomic_dec(&lru_count);
- clear_bit(GLF_LRU, &gl->gl_flags);
- freed++;
- gl->gl_lockref.count++;
- if (gl->gl_state != LM_ST_UNLOCKED)
- request_demote(gl, LM_ST_UNLOCKED, 0, false);
- gfs2_glock_queue_work(gl, 0);
- spin_unlock(&gl->gl_lockref.lock);
- cond_resched_lock(&lru_lock);
- }
- return freed;
-}
-
-/**
- * gfs2_scan_glock_lru - Scan the LRU looking for locks to demote
- * @nr: The number of entries to scan
- *
- * This function selects the entries on the LRU which are able to
- * be demoted, and then kicks off the process by calling
- * gfs2_dispose_glock_lru() above.
- */
-
-static unsigned long gfs2_scan_glock_lru(unsigned long nr)
-{
- struct gfs2_glock *gl, *next;
- LIST_HEAD(dispose);
- unsigned long freed = 0;
-
- spin_lock(&lru_lock);
- list_for_each_entry_safe(gl, next, &lru_list, gl_lru) {
- if (!nr--)
- break;
- if (can_free_glock(gl))
- list_move(&gl->gl_lru, &dispose);
- }
- if (!list_empty(&dispose))
- freed = gfs2_dispose_glock_lru(&dispose);
- spin_unlock(&lru_lock);
-
- return freed;
-}
-
-static unsigned long gfs2_glock_shrink_scan(struct shrinker *shrink,
- struct shrink_control *sc)
-{
- if (!(sc->gfp_mask & __GFP_FS))
- return SHRINK_STOP;
- return gfs2_scan_glock_lru(sc->nr_to_scan);
-}
-
-static unsigned long gfs2_glock_shrink_count(struct shrinker *shrink,
- struct shrink_control *sc)
-{
- return vfs_pressure_ratio(atomic_read(&lru_count));
-}
-
-static struct shrinker *glock_shrinker;
-
/**
* glock_hash_walk - Call a function for glock in a hash bucket
* @examiner: the function
@@ -2062,7 +1919,6 @@ static void thaw_glock(struct gfs2_glock *gl)
if (!lockref_get_not_dead(&gl->gl_lockref))
return;
- gfs2_glock_remove_from_lru(gl);
spin_lock(&gl->gl_lockref.lock);
set_bit(GLF_HAVE_REPLY, &gl->gl_flags);
gfs2_glock_queue_work(gl, 0);
@@ -2070,26 +1926,6 @@ static void thaw_glock(struct gfs2_glock *gl)
}
/**
- * clear_glock - look at a glock and see if we can free it from glock cache
- * @gl: the glock to look at
- *
- */
-
-static void clear_glock(struct gfs2_glock *gl)
-{
- gfs2_glock_remove_from_lru(gl);
-
- spin_lock(&gl->gl_lockref.lock);
- if (!__lockref_is_dead(&gl->gl_lockref)) {
- gl->gl_lockref.count++;
- if (gl->gl_state != LM_ST_UNLOCKED)
- request_demote(gl, LM_ST_UNLOCKED, 0, false);
- gfs2_glock_queue_work(gl, 0);
- }
- spin_unlock(&gl->gl_lockref.lock);
-}
-
-/**
* gfs2_glock_thaw - Thaw any frozen glocks
* @sdp: The super block
*
@@ -2115,7 +1951,7 @@ static void dump_glock_func(struct gfs2_glock *gl)
static void withdraw_glock(struct gfs2_glock *gl)
{
spin_lock(&gl->gl_lockref.lock);
- if (!__lockref_is_dead(&gl->gl_lockref)) {
+ if (!lockref_is_dead(&gl->gl_lockref)) {
/*
* We don't want to write back any more dirty data. Unlock the
* remaining inode and resource group glocks; this will cause
@@ -2135,22 +1971,17 @@ void gfs2_withdraw_glocks(struct gfs2_sbd *sdp)
}
/**
- * gfs2_gl_hash_clear - Empty out the glock hash table
+ * gfs2_wait_glocks - Wait for the remaining glocks to go away
* @sdp: the filesystem
*
* Called when unmounting the filesystem.
*/
-void gfs2_gl_hash_clear(struct gfs2_sbd *sdp)
+void gfs2_wait_glocks(struct gfs2_sbd *sdp)
{
unsigned long start = jiffies;
bool timed_out = false;
- set_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags);
- flush_workqueue(sdp->sd_glock_wq);
- glock_hash_walk(clear_glock, sdp);
- flush_workqueue(sdp->sd_glock_wq);
-
while (!timed_out) {
wait_event_timeout(sdp->sd_kill_wait,
!atomic_read(&sdp->sd_glock_disposal),
@@ -2273,8 +2104,6 @@ static const char *gflags2str(char *buf, const struct gfs2_glock *gl)
*p++ = 'F';
if (!list_empty(&gl->gl_holders))
*p++ = 'q';
- if (test_bit(GLF_LRU, gflags))
- *p++ = 'L';
if (gl->gl_object)
*p++ = 'o';
if (test_bit(GLF_BLOCKING, gflags))
@@ -2434,17 +2263,6 @@ int __init gfs2_glock_init(void)
if (ret < 0)
return ret;
- glock_shrinker = shrinker_alloc(0, "gfs2-glock");
- if (!glock_shrinker) {
- rhashtable_destroy(&gl_hash_table);
- return -ENOMEM;
- }
-
- glock_shrinker->count_objects = gfs2_glock_shrink_count;
- glock_shrinker->scan_objects = gfs2_glock_shrink_scan;
-
- shrinker_register(glock_shrinker);
-
for (i = 0; i < GLOCK_WAIT_TABLE_SIZE; i++)
init_waitqueue_head(glock_wait_table + i);
@@ -2453,7 +2271,6 @@ int __init gfs2_glock_init(void)
void gfs2_glock_exit(void)
{
- shrinker_free(glock_shrinker);
rhashtable_destroy(&gl_hash_table);
}
@@ -2483,7 +2300,7 @@ static void gfs2_glock_iter_next(struct gfs2_glock_iter *gi, loff_t n)
continue;
break;
} else {
- if (__lockref_is_dead(&gl->gl_lockref))
+ if (lockref_is_dead(&gl->gl_lockref))
continue;
n--;
}
diff --git a/fs/gfs2/glock.h b/fs/gfs2/glock.h
index 6341ac9b863f..8b9e22befefb 100644
--- a/fs/gfs2/glock.h
+++ b/fs/gfs2/glock.h
@@ -263,7 +263,7 @@ bool gfs2_queue_try_to_evict(struct gfs2_glock *gl);
bool gfs2_queue_verify_delete(struct gfs2_glock *gl, bool later);
void gfs2_cancel_delete_work(struct gfs2_glock *gl);
void gfs2_flush_delete_work(struct gfs2_sbd *sdp);
-void gfs2_gl_hash_clear(struct gfs2_sbd *sdp);
+void gfs2_wait_glocks(struct gfs2_sbd *sdp);
void gfs2_withdraw_glocks(struct gfs2_sbd *sdp);
void gfs2_glock_thaw(struct gfs2_sbd *sdp);
void gfs2_glock_free(struct gfs2_glock *gl);
diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h
index 61465777826a..dadb4d3c9d3d 100644
--- a/fs/gfs2/incore.h
+++ b/fs/gfs2/incore.h
@@ -321,7 +321,6 @@ enum {
GLF_INITIAL = 10,
GLF_HAVE_FROZEN_REPLY = 11,
GLF_INSTANTIATE_IN_PROG = 12, /* instantiate happening now */
- GLF_LRU = 13,
GLF_OBJECT = 14, /* Used only for tracing */
GLF_BLOCKING = 15,
GLF_TRY_TO_EVICT = 17, /* iopen glocks only */
@@ -355,7 +354,7 @@ struct gfs2_glock {
unsigned long gl_tchange;
void *gl_object;
- struct list_head gl_lru;
+ struct list_head gl_dead;
struct list_head gl_ail_list;
atomic_t gl_ail_count;
atomic_t gl_revokes;
@@ -601,7 +600,6 @@ enum {
SDF_DEMOTE = 5,
SDF_NOJOURNALID = 6,
SDF_RORECOVERY = 7, /* read only recovery */
- SDF_SKIP_DLM_UNLOCK = 8,
SDF_FORCE_AIL_FLUSH = 9,
SDF_FREEZE_INITIATOR = 10,
SDF_KILL = 15,
@@ -834,6 +832,9 @@ struct gfs2_sbd {
struct list_head sd_ail1_list;
struct list_head sd_ail2_list;
+ /* glocks */
+ spinlock_t sd_dead_lock;
+
/* For quiescing the filesystem */
struct gfs2_holder sd_freeze_gh;
struct mutex sd_freeze_mutex;
diff --git a/fs/gfs2/lock_dlm.c b/fs/gfs2/lock_dlm.c
index 7828ad0b6f5a..5a5ff3b5978e 100644
--- a/fs/gfs2/lock_dlm.c
+++ b/fs/gfs2/lock_dlm.c
@@ -126,7 +126,7 @@ static void gdlm_ast(void *arg)
clear_bit(GLF_BLOCKING, &gl->gl_flags);
/* If the glock is dead, we only react to a dlm_unlock() reply. */
- if (__lockref_is_dead(&gl->gl_lockref) &&
+ if (lockref_is_dead(&gl->gl_lockref) &&
gl->gl_lksb.sb_status != -DLM_EUNLOCK)
return;
@@ -182,7 +182,7 @@ static void gdlm_bast(void *arg, int mode)
{
struct gfs2_glock *gl = arg;
- if (__lockref_is_dead(&gl->gl_lockref))
+ if (lockref_is_dead(&gl->gl_lockref))
return;
switch (mode) {
@@ -329,7 +329,7 @@ static void gdlm_put_lock(struct gfs2_glock *gl)
uint32_t flags = 0;
int error;
- BUG_ON(!__lockref_is_dead(&gl->gl_lockref));
+ BUG_ON(!lockref_is_dead(&gl->gl_lockref));
if (test_bit(GLF_INITIAL, &gl->gl_flags)) {
gfs2_glock_free(gl);
@@ -347,7 +347,7 @@ static void gdlm_put_lock(struct gfs2_glock *gl)
* DLM_LOCK_PW mode, the lock value block (LVB) would be lost.
*/
- if (test_bit(SDF_SKIP_DLM_UNLOCK, &sdp->sd_flags) &&
+ if (test_bit(SDF_KILL, &sdp->sd_flags) &&
(!gl->gl_lksb.sb_lvbptr || gl->gl_state != LM_ST_EXCLUSIVE)) {
gfs2_glock_free_later(gl);
return;
diff --git a/fs/gfs2/main.c b/fs/gfs2/main.c
index 9d65719353fa..36c9c06e91ac 100644
--- a/fs/gfs2/main.c
+++ b/fs/gfs2/main.c
@@ -52,7 +52,6 @@ static void gfs2_init_glock_once(void *foo)
struct gfs2_glock *gl = foo;
INIT_LIST_HEAD(&gl->gl_holders);
- INIT_LIST_HEAD(&gl->gl_lru);
INIT_LIST_HEAD(&gl->gl_ail_list);
atomic_set(&gl->gl_ail_count, 0);
atomic_set(&gl->gl_revokes, 0);
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index 9b9e70f14d25..718e0da7dfce 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -123,6 +123,8 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb)
INIT_LIST_HEAD(&sdp->sd_ail1_list);
INIT_LIST_HEAD(&sdp->sd_ail2_list);
+ spin_lock_init(&sdp->sd_dead_lock);
+
init_rwsem(&sdp->sd_log_flush_lock);
atomic_set(&sdp->sd_log_in_flight, 0);
init_waitqueue_head(&sdp->sd_log_flush_wait);
@@ -1300,7 +1302,6 @@ fail_locking:
init_locking(sdp, &mount_gh, UNDO);
fail_lm:
complete_all(&sdp->sd_journal_ready);
- gfs2_gl_hash_clear(sdp);
gfs2_lm_unmount(sdp);
fail_debug:
gfs2_delete_debugfs_file(sdp);
@@ -1783,6 +1784,7 @@ static void gfs2_kill_sb(struct super_block *sb)
sdp->sd_master_dir = NULL;
shrink_dcache_sb(sb);
+ set_bit(SDF_KILL, &sdp->sd_flags);
gfs2_evict_inodes(sb);
/*
@@ -1790,7 +1792,6 @@ static void gfs2_kill_sb(struct super_block *sb)
* destroy_workqueue()) to ensure that any delete work that
* may be running will also see the SDF_KILL flag.
*/
- set_bit(SDF_KILL, &sdp->sd_flags);
gfs2_flush_delete_work(sdp);
destroy_workqueue(sdp->sd_delete_wq);
diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c
index 91e9975d25e8..001c8b39ca55 100644
--- a/fs/gfs2/quota.c
+++ b/fs/gfs2/quota.c
@@ -342,7 +342,7 @@ static void qd_put(struct gfs2_quota_data *qd)
if (lockref_put_or_lock(&qd->qd_lockref))
return;
- BUG_ON(__lockref_is_dead(&qd->qd_lockref));
+ BUG_ON(lockref_is_dead(&qd->qd_lockref));
sdp = qd->qd_sbd;
if (unlikely(!test_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags))) {
lockref_mark_dead(&qd->qd_lockref);
@@ -486,7 +486,7 @@ static bool qd_grab_sync(struct gfs2_sbd *sdp, struct gfs2_quota_data *qd,
qd->qd_sync_gen >= sync_gen)
goto out;
- if (__lockref_is_dead(&qd->qd_lockref))
+ if (lockref_is_dead(&qd->qd_lockref))
goto out;
qd->qd_lockref.count++;
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index 4d854556b529..06302c29340f 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -638,7 +638,7 @@ restart:
gfs2_clear_rgrpd(sdp);
gfs2_jindex_free(sdp);
/* Take apart glock structures and buffer lists */
- gfs2_gl_hash_clear(sdp);
+ gfs2_wait_glocks(sdp);
iput(sdp->sd_inode);
gfs2_delete_debugfs_file(sdp);
diff --git a/fs/gfs2/sys.c b/fs/gfs2/sys.c
index 7051db9dbea0..ea2c7b9e4a77 100644
--- a/fs/gfs2/sys.c
+++ b/fs/gfs2/sys.c
@@ -80,7 +80,6 @@ static ssize_t status_show(struct gfs2_sbd *sdp, char *buf)
"No Journal ID: %d\n"
"Mounted RO: %d\n"
"RO Recovery: %d\n"
- "Skip DLM Unlock: %d\n"
"Force AIL Flush: %d\n"
"FS Freeze Initiator: %d\n"
"FS Frozen: %d\n"
@@ -109,7 +108,6 @@ static ssize_t status_show(struct gfs2_sbd *sdp, char *buf)
test_bit(SDF_NOJOURNALID, &f),
(sb_rdonly(sdp->sd_vfs) ? 1 : 0),
test_bit(SDF_RORECOVERY, &f),
- test_bit(SDF_SKIP_DLM_UNLOCK, &f),
test_bit(SDF_FORCE_AIL_FLUSH, &f),
test_bit(SDF_FREEZE_INITIATOR, &f),
test_bit(SDF_FROZEN, &f),
diff --git a/fs/gfs2/trace_gfs2.h b/fs/gfs2/trace_gfs2.h
index 6fd39fcdd00e..bc40320ef239 100644
--- a/fs/gfs2/trace_gfs2.h
+++ b/fs/gfs2/trace_gfs2.h
@@ -56,7 +56,6 @@
{(1UL << GLF_HAVE_REPLY), "r" }, \
{(1UL << GLF_INITIAL), "a" }, \
{(1UL << GLF_HAVE_FROZEN_REPLY), "F" }, \
- {(1UL << GLF_LRU), "L" }, \
{(1UL << GLF_OBJECT), "o" }, \
{(1UL << GLF_BLOCKING), "b" }, \
{(1UL << GLF_INSTANTIATE_NEEDED), "n" }, \
diff --git a/fs/inode.c b/fs/inode.c
index a31aa7cb47f6..238fcd1cad6e 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2830,8 +2830,8 @@ struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 t
{
trace_inode_set_ctime_to_ts(inode, &ts);
set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
- inode->i_ctime_sec = ts.tv_sec;
- inode->i_ctime_nsec = ts.tv_nsec;
+ WRITE_ONCE(inode->i_ctime_sec, ts.tv_sec);
+ WRITE_ONCE(inode->i_ctime_nsec, ts.tv_nsec);
return ts;
}
EXPORT_SYMBOL(inode_set_ctime_to_ts);
@@ -2905,7 +2905,7 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
*/
cns = smp_load_acquire(&inode->i_ctime_nsec);
if (cns & I_CTIME_QUERIED) {
- struct timespec64 ctime = { .tv_sec = inode->i_ctime_sec,
+ struct timespec64 ctime = { .tv_sec = inode_get_ctime_sec(inode),
.tv_nsec = cns & ~I_CTIME_QUERIED };
if (timespec64_compare(&now, &ctime) <= 0) {
@@ -2917,7 +2917,7 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
mgtime_counter_inc(mg_ctime_updates);
/* No need to cmpxchg if it's exactly the same */
- if (cns == now.tv_nsec && inode->i_ctime_sec == now.tv_sec) {
+ if (cns == now.tv_nsec && inode_get_ctime_sec(inode) == now.tv_sec) {
trace_ctime_xchg_skip(inode, &now);
goto out;
}
@@ -2926,7 +2926,7 @@ retry:
/* Try to swap the nsec value into place. */
if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now.tv_nsec)) {
/* If swap occurred, then we're (mostly) done */
- inode->i_ctime_sec = now.tv_sec;
+ WRITE_ONCE(inode->i_ctime_sec, now.tv_sec);
trace_ctime_ns_xchg(inode, cns, now.tv_nsec, cur);
mgtime_counter_inc(mg_ctime_swaps);
} else {
@@ -2941,7 +2941,7 @@ retry:
goto retry;
}
/* Otherwise, keep the existing ctime */
- now.tv_sec = inode->i_ctime_sec;
+ now.tv_sec = inode_get_ctime_sec(inode);
now.tv_nsec = cur & ~I_CTIME_QUERIED;
}
out:
@@ -2974,7 +2974,7 @@ struct timespec64 inode_set_ctime_deleg(struct inode *inode, struct timespec64 u
/* pairs with try_cmpxchg below */
cur = smp_load_acquire(&inode->i_ctime_nsec);
cur_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
- cur_ts.tv_sec = inode->i_ctime_sec;
+ cur_ts.tv_sec = inode_get_ctime_sec(inode);
/* If the update is older than the existing value, skip it. */
if (timespec64_compare(&update, &cur_ts) <= 0)
@@ -3000,7 +3000,7 @@ struct timespec64 inode_set_ctime_deleg(struct inode *inode, struct timespec64 u
retry:
old = cur;
if (try_cmpxchg(&inode->i_ctime_nsec, &cur, update.tv_nsec)) {
- inode->i_ctime_sec = update.tv_sec;
+ WRITE_ONCE(inode->i_ctime_sec, update.tv_sec);
mgtime_counter_inc(mg_ctime_swaps);
return update;
}
@@ -3016,7 +3016,7 @@ retry:
goto retry;
/* Otherwise, it was a new timestamp. */
- cur_ts.tv_sec = inode->i_ctime_sec;
+ cur_ts.tv_sec = inode_get_ctime_sec(inode);
cur_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
return cur_ts;
}
diff --git a/fs/internal.h b/fs/internal.h
index 174f06357555..c658c8a5ebd5 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -363,3 +363,7 @@ int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
void pidfs_get_root(struct path *path);
void nsfs_get_root(struct path *path);
+void failfs_get_root(struct path *path);
+void __init failfs_init(void);
+bool failfs_mnt(const struct vfsmount *mnt);
+int failfs_current_chdir(void);
diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index f669fcdca2f0..0a5ebfda90f1 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -107,6 +107,12 @@ static void iomap_set_range_uptodate(struct folio *folio, size_t off,
folio_mark_uptodate(folio);
}
+void iomap_folio_mark_uptodate(struct folio *folio)
+{
+ iomap_set_range_uptodate(folio, 0, folio_size(folio));
+}
+EXPORT_SYMBOL_GPL(iomap_folio_mark_uptodate);
+
/*
* Find the next dirty block in the folio. end_blk is inclusive.
* If no dirty block is found, this will return end_blk + 1.
diff --git a/fs/isofs/compress.c b/fs/isofs/compress.c
index 3fda92358e22..f9869d62b850 100644
--- a/fs/isofs/compress.c
+++ b/fs/isofs/compress.c
@@ -293,6 +293,7 @@ static int zisofs_fill_pages(struct inode *inode, int full_page, int pcount,
memzero_page(*pages, poffset, PAGE_SIZE - poffset);
SetPageUptodate(*pages);
}
+ brelse(bh);
return 0;
}
diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c
index cc587cd25162..c7ca7603e97a 100644
--- a/fs/isofs/dir.c
+++ b/fs/isofs/dir.c
@@ -84,7 +84,7 @@ int get_acorn_filename(struct iso_directory_record *de,
*/
static int do_isofs_readdir(struct inode *inode, struct file *file,
struct dir_context *ctx,
- char *tmpname, struct iso_directory_record *tmpde)
+ char *tmpname)
{
unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
unsigned char bufbits = ISOFS_BUFFER_BITS(inode);
@@ -133,26 +133,7 @@ static int do_isofs_readdir(struct inode *inode, struct file *file,
offset_saved = offset;
offset += de_len;
- /* Make sure we have a full directory entry */
- if (offset >= bufsize) {
- int slop = bufsize - offset + de_len;
- memcpy(tmpde, de, slop);
- offset &= bufsize - 1;
- block++;
- brelse(bh);
- bh = NULL;
- if (offset) {
- bh = isofs_bread(inode, block);
- if (!bh)
- return 0;
- memcpy((void *) tmpde + slop, bh->b_data, offset);
- }
- de = tmpde;
- }
- /* Basic sanity check, whether name doesn't exceed dir entry */
- if (de_len < sizeof(struct iso_directory_record) ||
- de_len < de->name_len[0] +
- sizeof(struct iso_directory_record)) {
+ if (!isofs_dir_record_valid(de, offset_saved, bufsize)) {
printk(KERN_NOTICE "iso9660: Corrupted directory entry"
" in block %lu of inode %llu\n", block,
inode->i_ino);
@@ -254,16 +235,13 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx)
{
int result;
char *tmpname;
- struct iso_directory_record *tmpde;
struct inode *inode = file_inode(file);
- tmpname = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ tmpname = kmalloc(1024, GFP_KERNEL);
if (tmpname == NULL)
return -ENOMEM;
- tmpde = (struct iso_directory_record *) (tmpname+1024);
-
- result = do_isofs_readdir(inode, file, ctx, tmpname, tmpde);
+ result = do_isofs_readdir(inode, file, ctx, tmpname);
kfree(tmpname);
return result;
@@ -300,4 +278,3 @@ const struct inode_operations isofs_dir_inode_operations =
.fileattr_get = isofs_fileattr_get,
};
-
diff --git a/fs/isofs/export.c b/fs/isofs/export.c
index 78f80c1a5c54..4f7fa1d508a1 100644
--- a/fs/isofs/export.c
+++ b/fs/isofs/export.c
@@ -83,13 +83,21 @@ static struct dentry *isofs_export_get_parent(struct dentry *child)
/* This is the "." entry. */
de = (struct iso_directory_record*)bh->b_data;
+ if (!isofs_dir_record_valid(de, 0, child_inode->i_sb->s_blocksize) ||
+ isonum_711(de->name_len) != 1 || de->name[0] != 0) {
+ printk(KERN_ERR "isofs: Unable to find the \".\" directory for NFS.\n");
+ rv = ERR_PTR(-EACCES);
+ goto out;
+ }
/* The ".." entry is always the second entry. */
parent_offset = (unsigned long)isonum_711(de->length);
de = (struct iso_directory_record*)(bh->b_data + parent_offset);
/* Verify it is in fact the ".." entry. */
- if ((isonum_711(de->name_len) != 1) || (de->name[0] != 1)) {
+ if (!isofs_dir_record_valid(de, parent_offset,
+ child_inode->i_sb->s_blocksize) ||
+ isonum_711(de->name_len) != 1 || de->name[0] != 1) {
printk(KERN_ERR "isofs: Unable to find the \"..\" "
"directory for NFS.\n");
rv = ERR_PTR(-EACCES);
diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h
index 0ec8b24a42ed..dacb9cdae4fd 100644
--- a/fs/isofs/isofs.h
+++ b/fs/isofs/isofs.h
@@ -115,6 +115,9 @@ struct inode; /* To make gcc happy */
extern int parse_rock_ridge_inode(struct iso_directory_record *, struct inode *, int relocated);
extern int get_rock_ridge_filename(struct iso_directory_record *, char *, struct inode *);
extern int isofs_name_translate(struct iso_directory_record *, char *, struct inode *);
+bool isofs_dir_record_valid(struct iso_directory_record *de,
+ unsigned long offset,
+ unsigned long bufsize);
int get_joliet_filename(struct iso_directory_record *, unsigned char *, struct inode *);
int get_acorn_filename(struct iso_directory_record *, char *, struct inode *);
diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c
index 3ace3d6a55e7..010682f5901a 100644
--- a/fs/isofs/namei.c
+++ b/fs/isofs/namei.c
@@ -10,6 +10,26 @@
#include <linux/gfp.h>
#include "isofs.h"
+bool isofs_dir_record_valid(struct iso_directory_record *de,
+ unsigned long offset,
+ unsigned long bufsize)
+{
+ unsigned int len;
+ unsigned int name_len;
+ unsigned long min_len = offsetof(struct iso_directory_record, name);
+
+ if (offset > bufsize || bufsize - offset < min_len)
+ return false;
+
+ len = isonum_711(de->length);
+ name_len = isonum_711(de->name_len);
+ if (len < min_len || name_len > len - min_len)
+ return false;
+ if (len > bufsize - offset)
+ return false;
+ return true;
+}
+
static int
isofs_cmp(struct dentry *dentry, const char *compare, int dlen)
{
@@ -28,7 +48,7 @@ isofs_cmp(struct dentry *dentry, const char *compare, int dlen)
static unsigned long
isofs_find_entry(struct inode *dir, struct dentry *dentry,
unsigned long *block_rv, unsigned long *offset_rv,
- char *tmpname, struct iso_directory_record *tmpde)
+ char *tmpname)
{
unsigned long bufsize = ISOFS_BUFFER_SIZE(dir);
unsigned char bufbits = ISOFS_BUFFER_BITS(dir);
@@ -71,33 +91,15 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry,
offset += de_len;
f_pos += de_len;
- /* Make sure we have a full directory entry */
- if (offset >= bufsize) {
- int slop = bufsize - offset + de_len;
- memcpy(tmpde, de, slop);
- offset &= bufsize - 1;
- block++;
- brelse(bh);
- bh = NULL;
- if (offset) {
- bh = isofs_bread(dir, block);
- if (!bh)
- return 0;
- memcpy((void *) tmpde + slop, bh->b_data, offset);
- }
- de = tmpde;
- }
-
- dlen = de->name_len[0];
- dpnt = de->name;
- /* Basic sanity check, whether name doesn't exceed dir entry */
- if (de_len < dlen + sizeof(struct iso_directory_record)) {
+ if (!isofs_dir_record_valid(de, offset_saved, bufsize)) {
printk(KERN_NOTICE "iso9660: Corrupted directory entry"
" in block %lu of inode %llu\n", block,
dir->i_ino);
brelse(bh);
return 0;
}
+ dlen = de->name_len[0];
+ dpnt = de->name;
if (sbi->s_rock &&
((i = get_rock_ridge_filename(de, tmpname, dir)))) {
@@ -149,17 +151,14 @@ struct dentry *isofs_lookup(struct inode *dir, struct dentry *dentry, unsigned i
unsigned long block;
unsigned long offset;
struct inode *inode;
- struct page *page;
+ char *tmpname;
- page = alloc_page(GFP_USER);
- if (!page)
+ tmpname = kmalloc(1024, GFP_USER);
+ if (!tmpname)
return ERR_PTR(-ENOMEM);
- found = isofs_find_entry(dir, dentry,
- &block, &offset,
- page_address(page),
- 1024 + page_address(page));
- __free_page(page);
+ found = isofs_find_entry(dir, dentry, &block, &offset, tmpname);
+ kfree(tmpname);
inode = found ? isofs_iget(dir->i_sb, block, offset) : NULL;
diff --git a/fs/libfs.c b/fs/libfs.c
index 5a0d276379d1..27d7dc16fcb0 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1559,9 +1559,12 @@ int simple_fsync_noflush(struct file *file, loff_t start, loff_t end,
if (err)
return err;
- if (!(inode_state_read_once(inode) & I_DIRTY_ALL))
+ if (!(inode_state_read_once(inode) &
+ (I_DIRTY_ALL | I_SYNC | I_METADATA_WRITEBACK)))
goto out;
- if (datasync && !(inode_state_read_once(inode) & I_DIRTY_DATASYNC))
+ if (datasync &&
+ !(inode_state_read_once(inode) &
+ (I_DIRTY_DATASYNC | I_SYNC | I_METADATA_WRITEBACK)))
goto out;
ret = sync_inode_metadata(inode, 1);
diff --git a/fs/lockd/nlm3xdr_gen.c b/fs/lockd/nlm3xdr_gen.c
index 9ed5a41b5daf..352a694ca0f5 100644
--- a/fs/lockd/nlm3xdr_gen.c
+++ b/fs/lockd/nlm3xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x
-// XDR specification modification time: Thu Apr 23 10:56:34 2026
+// XDR specification modification time: Mon Jun 29 20:39:34 2026
#include <linux/sunrpc/svc.h>
diff --git a/fs/lockd/nlm3xdr_gen.h b/fs/lockd/nlm3xdr_gen.h
index c99038e99805..d24cbb887b7f 100644
--- a/fs/lockd/nlm3xdr_gen.h
+++ b/fs/lockd/nlm3xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x */
-/* XDR specification modification time: Thu Apr 23 10:56:34 2026 */
+/* XDR specification modification time: Mon Jun 29 20:39:34 2026 */
#ifndef _LINUX_XDRGEN_NLM3_DECL_H
#define _LINUX_XDRGEN_NLM3_DECL_H
diff --git a/fs/lockd/nlm4xdr_gen.c b/fs/lockd/nlm4xdr_gen.c
index 1c8c221db456..004ea01c689e 100644
--- a/fs/lockd/nlm4xdr_gen.c
+++ b/fs/lockd/nlm4xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nlm4.x
-// XDR specification modification time: Thu Dec 25 13:10:19 2025
+// XDR specification modification time: Mon Jun 29 20:39:36 2026
#include <linux/sunrpc/svc.h>
@@ -20,6 +20,16 @@ xdrgen_decode_fsh4_mode(struct xdr_stream *xdr, fsh4_mode *ptr)
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case fsm_DN:
+ case fsm_DR:
+ case fsm_DW:
+ case fsm_DRW:
+ break;
+ default:
+ return false;
+ }
*ptr = val;
return true;
}
@@ -31,6 +41,16 @@ xdrgen_decode_fsh4_access(struct xdr_stream *xdr, fsh4_access *ptr)
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case fsa_NONE:
+ case fsa_R:
+ case fsa_W:
+ case fsa_RW:
+ break;
+ default:
+ return false;
+ }
*ptr = val;
return true;
}
@@ -62,7 +82,30 @@ xdrgen_decode_int32(struct xdr_stream *xdr, int32 *ptr)
static bool __maybe_unused
xdrgen_decode_nlm4_stats(struct xdr_stream *xdr, nlm4_stats *ptr)
{
- return xdr_stream_decode_be32(xdr, ptr) == 0;
+ __be32 raw;
+ u32 val;
+
+ if (xdr_stream_decode_be32(xdr, &raw) < 0)
+ return false;
+ val = be32_to_cpu(raw);
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case NLM4_GRANTED:
+ case NLM4_DENIED:
+ case NLM4_DENIED_NOLOCKS:
+ case NLM4_BLOCKED:
+ case NLM4_DENIED_GRACE_PERIOD:
+ case NLM4_DEADLCK:
+ case NLM4_ROFS:
+ case NLM4_STALE_FH:
+ case NLM4_FBIG:
+ case NLM4_FAILED:
+ break;
+ default:
+ return false;
+ }
+ *ptr = raw;
+ return true;
}
static bool __maybe_unused
diff --git a/fs/lockd/nlm4xdr_gen.h b/fs/lockd/nlm4xdr_gen.h
index b6008b296a3e..b5898f0e0689 100644
--- a/fs/lockd/nlm4xdr_gen.h
+++ b/fs/lockd/nlm4xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nlm4.x */
-/* XDR specification modification time: Thu Dec 25 13:10:19 2025 */
+/* XDR specification modification time: Mon Jun 29 20:39:36 2026 */
#ifndef _LINUX_XDRGEN_NLM4_DECL_H
#define _LINUX_XDRGEN_NLM4_DECL_H
diff --git a/fs/lockd/share.h b/fs/lockd/share.h
index 1ec3ccdb2aef..a12b6c454f58 100644
--- a/fs/lockd/share.h
+++ b/fs/lockd/share.h
@@ -8,9 +8,14 @@
#ifndef _LOCKD_SHARE_H
#define _LOCKD_SHARE_H
+#include <linux/bits.h>
+
/* Synthetic svid for lockowner lookup during share operations */
#define LOCKD_SHARE_SVID (~(u32)0)
+/* One bit per (access, deny) pair; index = (access << 2) | deny */
+#define LOCKD_FSH_BIT(a, d) BIT(((a) << 2) | (d))
+
/*
* DOS share for a specific file
*/
@@ -21,12 +26,13 @@ struct lockd_share {
struct xdr_netobj s_owner; /* owner handle */
u32 s_access; /* access mode */
u32 s_mode; /* deny mode */
+ u16 s_access_deny_bmap; /* held (access, deny) pairs */
};
__be32 nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file,
struct xdr_netobj *oh, u32 access, u32 mode);
__be32 nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
- struct xdr_netobj *oh);
+ struct xdr_netobj *oh, u32 access, u32 mode);
void nlmsvc_traverse_shares(struct nlm_host *, struct nlm_file *,
nlm_host_match_fn_t);
diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c
index b73004a7987e..d104aab0881a 100644
--- a/fs/lockd/svc4proc.c
+++ b/fs/lockd/svc4proc.c
@@ -1079,7 +1079,9 @@ static __be32 nlm4svc_proc_unshare(struct svc_rqst *rqstp)
if (resp->xdrgen.stat)
goto out;
- resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh);
+ resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh,
+ argp->xdrgen.share.access,
+ argp->xdrgen.share.mode);
nlmsvc_release_lockowner(lock);
diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c
index d410b8c69893..a1f9d66c2981 100644
--- a/fs/lockd/svcproc.c
+++ b/fs/lockd/svcproc.c
@@ -1098,7 +1098,9 @@ static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp)
if (resp->xdrgen.stat)
goto out;
- resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh);
+ resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh,
+ argp->xdrgen.share.access,
+ argp->xdrgen.share.mode);
nlmsvc_release_lockowner(lock);
diff --git a/fs/lockd/svcshare.c b/fs/lockd/svcshare.c
index 5ac0ec25d62d..a58b7035b58b 100644
--- a/fs/lockd/svcshare.c
+++ b/fs/lockd/svcshare.c
@@ -25,6 +25,25 @@ nlm_cmp_owner(struct lockd_share *share, struct xdr_netobj *oh)
&& !memcmp(share->s_owner.data, oh->data, oh->len);
}
+/*
+ * Recompute s_access / s_mode as the union of every (access, deny) pair
+ * whose bit is currently set in s_access_deny_bmap.
+ */
+static void nlm_recompute_share(struct lockd_share *share)
+{
+ u32 new_access = 0, new_mode = 0;
+ unsigned int i;
+
+ for (i = 0; i < 16; i++) {
+ if (share->s_access_deny_bmap & BIT(i)) {
+ new_access |= i >> 2;
+ new_mode |= i & 3;
+ }
+ }
+ share->s_access = new_access;
+ share->s_mode = new_mode;
+}
+
/**
* nlmsvc_share_file - create a share
* @host: Network client peer
@@ -64,12 +83,13 @@ nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file,
share->s_host = host;
share->s_owner.data = ohdata;
share->s_owner.len = oh->len;
+ share->s_access_deny_bmap = 0;
share->s_next = file->f_shares;
file->f_shares = share;
update:
- share->s_access = access;
- share->s_mode = mode;
+ share->s_access_deny_bmap |= LOCKD_FSH_BIT(access, mode);
+ nlm_recompute_share(share);
return nlm_granted;
}
@@ -78,12 +98,14 @@ update:
* @host: Network client peer
* @file: File to be unshared
* @oh: Share owner handle
+ * @access: Access mode of the SHARE being released
+ * @mode: Deny mode of the SHARE being released
*
* Returns an NLM status code.
*/
__be32
nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
- struct xdr_netobj *oh)
+ struct xdr_netobj *oh, u32 access, u32 mode)
{
struct lockd_share *share, **shpp;
@@ -93,8 +115,12 @@ nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
for (shpp = &file->f_shares; (share = *shpp) != NULL;
shpp = &share->s_next) {
if (share->s_host == host && nlm_cmp_owner(share, oh)) {
- *shpp = share->s_next;
- kfree(share);
+ share->s_access_deny_bmap &= ~LOCKD_FSH_BIT(access, mode);
+ nlm_recompute_share(share);
+ if (!share->s_access_deny_bmap) {
+ *shpp = share->s_next;
+ kfree(share);
+ }
return nlm_granted;
}
}
diff --git a/fs/minix/dir.c b/fs/minix/dir.c
index 361d26d87d2e..2ca16f849d5a 100644
--- a/fs/minix/dir.c
+++ b/fs/minix/dir.c
@@ -23,7 +23,7 @@ const struct file_operations minix_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.iterate_shared = minix_readdir,
- .fsync = minix_fsync,
+ .fsync = simple_fsync,
};
/*
diff --git a/fs/minix/file.c b/fs/minix/file.c
index 86e5943cd2ff..02aabbdb5dea 100644
--- a/fs/minix/file.c
+++ b/fs/minix/file.c
@@ -10,13 +10,6 @@
#include <linux/buffer_head.h>
#include "minix.h"
-int minix_fsync(struct file *file, loff_t start, loff_t end, int datasync)
-{
- return mmb_fsync(file,
- &minix_i(file->f_mapping->host)->i_metadata_bhs,
- start, end, datasync);
-}
-
/*
* We have mostly NULLs here: the current defaults are OK for
* the minix filesystem.
@@ -26,7 +19,7 @@ const struct file_operations minix_file_operations = {
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.mmap_prepare = generic_file_mmap_prepare,
- .fsync = minix_fsync,
+ .fsync = simple_fsync,
.splice_read = filemap_splice_read,
};
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
index c30cc590698d..daf83e4ff25c 100644
--- a/fs/minix/inode.c
+++ b/fs/minix/inode.c
@@ -24,6 +24,8 @@
static int minix_write_inode(struct inode *inode,
struct writeback_control *wbc);
+static int minix_sync_inode_metadata(struct inode *inode,
+ struct writeback_control *wbc);
static int minix_statfs(struct dentry *dentry, struct kstatfs *buf);
void __minix_error_inode(struct inode *inode, const char *function,
@@ -128,6 +130,7 @@ static const struct super_operations minix_sops = {
.alloc_inode = minix_alloc_inode,
.free_inode = minix_free_in_core_inode,
.write_inode = minix_write_inode,
+ .sync_inode_metadata = minix_sync_inode_metadata,
.evict_inode = minix_evict_inode,
.put_super = minix_put_super,
.statfs = minix_statfs,
@@ -630,7 +633,7 @@ struct inode *minix_iget(struct super_block *sb, unsigned long ino)
/*
* The minix V1 function to synchronize an inode.
*/
-static struct buffer_head * V1_minix_update_inode(struct inode * inode)
+static int V1_minix_update_inode(struct inode * inode)
{
struct buffer_head * bh;
struct minix_inode * raw_inode;
@@ -639,7 +642,7 @@ static struct buffer_head * V1_minix_update_inode(struct inode * inode)
raw_inode = minix_V1_raw_inode(inode->i_sb, inode->i_ino, &bh);
if (!raw_inode)
- return NULL;
+ return -EIO;
raw_inode->i_mode = inode->i_mode;
raw_inode->i_uid = fs_high2lowuid(i_uid_read(inode));
raw_inode->i_gid = fs_high2lowgid(i_gid_read(inode));
@@ -651,13 +654,15 @@ static struct buffer_head * V1_minix_update_inode(struct inode * inode)
else for (i = 0; i < 9; i++)
raw_inode->i_zone[i] = minix_inode->u.i1_data[i];
mark_buffer_dirty(bh);
- return bh;
+ brelse(bh);
+ set_inode_metadata_writeback(inode);
+ return 0;
}
/*
* The minix V2 function to synchronize an inode.
*/
-static struct buffer_head * V2_minix_update_inode(struct inode * inode)
+static int V2_minix_update_inode(struct inode * inode)
{
struct buffer_head * bh;
struct minix2_inode * raw_inode;
@@ -666,7 +671,7 @@ static struct buffer_head * V2_minix_update_inode(struct inode * inode)
raw_inode = minix_V2_raw_inode(inode->i_sb, inode->i_ino, &bh);
if (!raw_inode)
- return NULL;
+ return -EIO;
raw_inode->i_mode = inode->i_mode;
raw_inode->i_uid = fs_high2lowuid(i_uid_read(inode));
raw_inode->i_gid = fs_high2lowgid(i_gid_read(inode));
@@ -680,29 +685,42 @@ static struct buffer_head * V2_minix_update_inode(struct inode * inode)
else for (i = 0; i < 10; i++)
raw_inode->i_zone[i] = minix_inode->u.i2_data[i];
mark_buffer_dirty(bh);
- return bh;
+ brelse(bh);
+ set_inode_metadata_writeback(inode);
+ return 0;
}
static int minix_write_inode(struct inode *inode, struct writeback_control *wbc)
{
+ if (INODE_VERSION(inode) == MINIX_V1)
+ return V1_minix_update_inode(inode);
+ return V2_minix_update_inode(inode);
+}
+
+static int minix_sync_inode_metadata(struct inode *inode,
+ struct writeback_control *wbc)
+{
int err = 0;
struct buffer_head *bh;
+ void *raw_inode;
if (INODE_VERSION(inode) == MINIX_V1)
- bh = V1_minix_update_inode(inode);
+ raw_inode = minix_V1_raw_inode(inode->i_sb, inode->i_ino, &bh);
else
- bh = V2_minix_update_inode(inode);
- if (!bh)
+ raw_inode = minix_V2_raw_inode(inode->i_sb, inode->i_ino, &bh);
+ if (!raw_inode)
return -EIO;
- if (wbc->sync_mode == WB_SYNC_ALL && buffer_dirty(bh)) {
- sync_dirty_buffer(bh);
- if (buffer_req(bh) && !buffer_uptodate(bh)) {
- printk("IO error syncing minix inode [%s:%08llx]\n",
- inode->i_sb->s_id, inode->i_ino);
- err = -EIO;
- }
+ err = mmb_sync(&minix_i(inode)->i_metadata_bhs);
+ if (err)
+ goto out;
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ printk("IO error syncing minix inode [%s:%08llx]\n",
+ inode->i_sb->s_id, inode->i_ino);
+ err = -EIO;
}
- brelse (bh);
+out:
+ brelse(bh);
return err;
}
diff --git a/fs/minix/minix.h b/fs/minix/minix.h
index 9e52d4302f0d..78722ce22e1e 100644
--- a/fs/minix/minix.h
+++ b/fs/minix/minix.h
@@ -59,7 +59,6 @@ int minix_getattr(struct mnt_idmap *, const struct path *,
struct kstat *, u32, unsigned int);
int minix_prepare_chunk(struct folio *folio, loff_t pos, unsigned len);
struct mapping_metadata_bhs *minix_get_metadata_bhs(struct inode *inode);
-int minix_fsync(struct file *file, loff_t start, loff_t end, int datasync);
extern void V1_minix_truncate(struct inode *);
extern void V2_minix_truncate(struct inode *);
diff --git a/fs/namei.c b/fs/namei.c
index e4ef48583a3b..01d905ade24d 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4397,19 +4397,41 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry
* An error code is returned on failure.
*/
static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
- const struct open_flags *op,
- bool got_write, struct delegated_inode *delegated_inode)
+ const struct open_flags *op)
{
+ struct delegated_inode delegated_inode = { };
struct mnt_idmap *idmap;
struct dentry *dir = nd->path.dentry;
struct inode *dir_inode = dir->d_inode;
- int open_flag = op->open_flag;
+ int open_flag;
struct dentry *dentry;
- int error, create_error = 0;
- umode_t mode = op->mode;
+ int error, create_error;
+ umode_t mode;
+ bool got_write;
- if (unlikely(IS_DEADDIR(dir_inode)))
- return ERR_PTR(-ENOENT);
+retry:
+ open_flag = op->open_flag;
+ got_write = false;
+ mode = op->mode;
+ create_error = 0;
+
+ if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
+ got_write = !mnt_want_write(nd->path.mnt);
+ /*
+ * do _not_ fail yet - we might not need that or fail with
+ * a different error; let lookup_open() decide; we'll be
+ * dropping this one anyway.
+ */
+ }
+ if (open_flag & O_CREAT)
+ inode_lock(dir_inode);
+ else
+ inode_lock_shared(dir_inode);
+
+ if (unlikely(IS_DEADDIR(dir_inode))) {
+ dentry = ERR_PTR(-ENOENT);
+ goto out;
+ }
file->f_mode &= ~FMODE_CREATED;
dentry = d_lookup(dir, &nd->last);
@@ -4417,7 +4439,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
if (!dentry) {
dentry = d_alloc_parallel(dir, &nd->last);
if (IS_ERR(dentry))
- return dentry;
+ goto out;
}
if (d_in_lookup(dentry))
break;
@@ -4433,7 +4455,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
}
if (dentry->d_inode) {
/* Cached positive dentry: will open in f_op->open */
- return dentry;
+ goto out;
}
if (open_flag & O_CREAT)
@@ -4454,7 +4476,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
if (open_flag & O_CREAT) {
if (open_flag & O_EXCL)
open_flag &= ~O_TRUNC;
- mode = vfs_prepare_mode(idmap, dir->d_inode, mode, mode, mode);
+ mode = vfs_prepare_mode(idmap, dir_inode, mode, mode, mode);
if (likely(got_write))
create_error = may_o_create(idmap, &nd->path,
dentry, mode);
@@ -4469,7 +4491,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
dentry = atomic_open(&nd->path, dentry, file, open_flag, mode);
if (unlikely(create_error) && dentry == ERR_PTR(-ENOENT))
dentry = ERR_PTR(create_error);
- return dentry;
+ goto out;
}
if (d_in_lookup(dentry)) {
@@ -4489,7 +4511,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
/* Negative dentry, just create the file */
if (!dentry->d_inode && (open_flag & O_CREAT)) {
/* but break the directory lease first! */
- error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_inode);
+ error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_inode);
if (error)
goto out_dput;
@@ -4508,12 +4530,133 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
error = create_error;
goto out_dput;
}
+out:
+ if (!IS_ERR(dentry)) {
+ if (file->f_mode & FMODE_CREATED)
+ fsnotify_create(dir_inode, dentry);
+ if (file->f_mode & FMODE_OPENED)
+ fsnotify_open(file);
+ }
+ if ((open_flag & O_CREAT) || create_error)
+ inode_unlock(dir_inode);
+ else
+ inode_unlock_shared(dir_inode);
+
+ if (got_write)
+ mnt_drop_write(nd->path.mnt);
+
+ if (is_delegated(&delegated_inode)) {
+ /* Must have come through out_dput: dentry is an ERR_PTR() */
+ error = break_deleg_wait(&delegated_inode);
+
+ if (!error)
+ goto retry;
+ dentry = ERR_PTR(error);
+ }
+
return dentry;
out_dput:
dput(dentry);
- return ERR_PTR(error);
+ dentry = ERR_PTR(error);
+ goto out;
+}
+
+/**
+ * vfs_lookup_open - open and possibly create a regular file
+ * @parent: directory to contain file
+ * @last: final component of file name
+ * @open_flag: O_flags
+ * @mode: initial permissions for file
+ *
+ * Open a file after lookup and/or create. This provides similar
+ * functionality open_last_lookups() for non-VFS users, particularly
+ * nfsd.
+ * It uses ->atomic_open or ->lookup / ->create / ->open as appropriate.
+ *
+ * If the fs object found is not a regular file then an error is returned.
+ * In some cases, related errors are repurposed so that the caller can
+ * determine the type of file found from the error.
+ * -EISDIR : a directory was found
+ * -ELOOP : a symlink was found
+ * -ENODEV : a block or character device special file was found
+ * -EFTYPE : any other non-regular file was found, such as FIFO or SOCK.
+ * or ->atomic_open responded to __O_REGULAR.
+ *
+ * Returns: the opened struct file, or an error.
+ */
+struct file *vfs_lookup_open(struct path *parent, struct qstr *last,
+ int open_flag, umode_t mode)
+{
+ struct file *file __free(fput) = NULL;
+ struct nameidata nd = {};
+ struct open_flags op = {};
+ struct dentry *dentry;
+ int error = 0;
+
+ WARN_ONCE(mode & ~S_IALLUGO, "mode must only have permission bits");
+ WARN_ONCE(open_flag & ~(O_ACCMODE|O_CREAT|O_EXCL|O_TRUNC|__O_REGULAR),
+ "open_flag has unsupported flags");
+
+ mode |= S_IFREG;
+ open_flag |= __O_REGULAR;
+
+ error = lookup_noperm_common(last, parent->dentry);
+ if (error)
+ return ERR_PTR(error);
+
+ file = alloc_empty_file(open_flag, current_cred());
+ if (IS_ERR(file))
+ return file;
+
+ nd.path = *parent;
+ nd.last = *last;
+ nd.flags = LOOKUP_OPEN;
+ if (open_flag & O_CREAT) {
+ nd.flags |= LOOKUP_CREATE;
+ if (open_flag & O_EXCL)
+ nd.flags |= LOOKUP_EXCL;
+ }
+ op.open_flag = open_flag;
+ op.mode = mode;
+ dentry = lookup_open(&nd, file, &op);
+
+ if (IS_ERR(dentry))
+ return ERR_CAST(dentry);
+
+ if (d_really_is_negative(dentry)) {
+ error = -ENOENT;
+ } else if (!(file->f_mode & FMODE_CREATED) && (open_flag & O_EXCL)) {
+ error = -EEXIST;
+ } else if ((dentry->d_inode->i_mode & S_IFMT) != S_IFREG) {
+ switch (dentry->d_inode->i_mode & S_IFMT) {
+ case S_IFDIR:
+ error = -EISDIR;
+ break;
+ case S_IFLNK:
+ error = -ELOOP;
+ break;
+ case S_IFBLK:
+ case S_IFCHR:
+ error = -ENODEV;
+ break;
+ case S_IFIFO:
+ case S_IFSOCK:
+ default:
+ error = -EFTYPE;
+ break;
+ }
+ } else if (!(file->f_mode & FMODE_OPENED)) {
+ nd.path.dentry = dentry;
+ error = vfs_open(&nd.path, file);
+ }
+ dput(dentry);
+
+ if (error)
+ return ERR_PTR(error);
+ return no_free_ptr(file);
}
+EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, "nfsd");
static inline bool trailing_slashes(struct nameidata *nd)
{
@@ -4554,10 +4697,7 @@ static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)
static const char *open_last_lookups(struct nameidata *nd,
struct file *file, const struct open_flags *op)
{
- struct delegated_inode delegated_inode = { };
- struct dentry *dir = nd->path.dentry;
int open_flag = op->open_flag;
- bool got_write = false;
struct dentry *dentry;
const char *res;
@@ -4586,44 +4726,10 @@ static const char *open_last_lookups(struct nameidata *nd,
return ERR_PTR(-ECHILD);
}
}
-retry:
- if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
- got_write = !mnt_want_write(nd->path.mnt);
- /*
- * do _not_ fail yet - we might not need that or fail with
- * a different error; let lookup_open() decide; we'll be
- * dropping this one anyway.
- */
- }
- if (open_flag & O_CREAT)
- inode_lock(dir->d_inode);
- else
- inode_lock_shared(dir->d_inode);
- dentry = lookup_open(nd, file, op, got_write, &delegated_inode);
- if (!IS_ERR(dentry)) {
- if (file->f_mode & FMODE_CREATED)
- fsnotify_create(dir->d_inode, dentry);
- if (file->f_mode & FMODE_OPENED)
- fsnotify_open(file);
- }
- if (open_flag & O_CREAT)
- inode_unlock(dir->d_inode);
- else
- inode_unlock_shared(dir->d_inode);
-
- if (got_write)
- mnt_drop_write(nd->path.mnt);
-
- if (IS_ERR(dentry)) {
- if (is_delegated(&delegated_inode)) {
- int error = break_deleg_wait(&delegated_inode);
- if (!error)
- goto retry;
- return ERR_PTR(error);
- }
+ dentry = lookup_open(nd, file, op);
+ if (IS_ERR(dentry))
return ERR_CAST(dentry);
- }
if (file->f_mode & (FMODE_OPENED | FMODE_CREATED)) {
dput(nd->path.dentry);
diff --git a/fs/namespace.c b/fs/namespace.c
index 6b81b77f9303..1ecd96c918b3 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -6270,9 +6270,6 @@ void __init mnt_init(void)
HASH_ZERO,
&mp_hash_shift, &mp_hash_mask, 0, 0);
- if (!mount_hashtable || !mountpoint_hashtable)
- panic("Failed to allocate mount hash table\n");
-
super_dev_init();
kernfs_init();
@@ -6287,6 +6284,7 @@ void __init mnt_init(void)
shmem_init();
init_rootfs();
init_mount_tree();
+ failfs_init();
}
void put_mnt_ns(struct mnt_namespace *ns)
@@ -6296,7 +6294,7 @@ void put_mnt_ns(struct mnt_namespace *ns)
guard(namespace_excl)();
emptied_ns = ns;
guard(mount_writer)();
- umount_tree(ns->root, 0);
+ umount_tree(ns->root, UMOUNT_CONNECTED);
}
struct vfsmount *kern_mount(struct file_system_type *type)
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 24a8a5418e31..7fdfa4f27e34 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -102,8 +102,10 @@ static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq,
added = rolling_buffer_load_from_ra(&rreq->buffer, ractl,
&put_batch);
- if (added < 0)
+ if (added < 0) {
+ folio_batch_release(&put_batch);
return added;
+ }
rreq->submitted += added;
}
folio_batch_release(&put_batch);
@@ -359,7 +361,7 @@ void netfs_readahead(struct readahead_control *ractl)
netfs_rreq_expand(rreq, ractl);
rreq->submitted = rreq->start;
- if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0)
+ if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0)
goto cleanup_free;
netfs_read_to_pagecache(rreq, ractl);
@@ -378,10 +380,10 @@ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct fo
{
ssize_t added;
- if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0)
+ if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0)
return -ENOMEM;
- added = rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags);
+ added = rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags, rreq->gfp);
if (added < 0)
return added;
rreq->submitted = rreq->start + added;
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index d889caa401dc..420ee7b26580 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -43,6 +43,7 @@ extern struct list_head netfs_io_requests;
extern spinlock_t netfs_proc_lock;
extern mempool_t netfs_request_pool;
extern mempool_t netfs_subrequest_pool;
+extern mempool_t netfs_folioq_pool;
#ifdef CONFIG_PROC_FS
static inline void netfs_proc_add_rreq(struct netfs_io_request *rreq)
diff --git a/fs/netfs/main.c b/fs/netfs/main.c
index 73da6c9f5777..927badf3989d 100644
--- a/fs/netfs/main.c
+++ b/fs/netfs/main.c
@@ -28,6 +28,7 @@ static struct kmem_cache *netfs_request_slab;
static struct kmem_cache *netfs_subrequest_slab;
mempool_t netfs_request_pool;
mempool_t netfs_subrequest_pool;
+mempool_t netfs_folioq_pool;
#ifdef CONFIG_PROC_FS
LIST_HEAD(netfs_io_requests);
@@ -108,6 +109,9 @@ static int __init netfs_init(void)
{
int ret = -ENOMEM;
+ if (mempool_init_kmalloc_pool(&netfs_folioq_pool, 100, sizeof(struct folio_queue)) < 0)
+ goto error_folioq_pool;
+
netfs_request_slab = kmem_cache_create("netfs_request",
sizeof(struct netfs_io_request), 0,
SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT,
@@ -160,6 +164,8 @@ error_subreq:
error_reqpool:
kmem_cache_destroy(netfs_request_slab);
error_req:
+ mempool_exit(&netfs_folioq_pool);
+error_folioq_pool:
return ret;
}
fs_initcall(netfs_init);
@@ -172,5 +178,6 @@ static void __exit netfs_exit(void)
kmem_cache_destroy(netfs_subrequest_slab);
mempool_exit(&netfs_request_pool);
kmem_cache_destroy(netfs_request_slab);
+ mempool_exit(&netfs_folioq_pool);
}
module_exit(netfs_exit);
diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c
index b8c4918d3dcd..01461a74642d 100644
--- a/fs/netfs/objects.c
+++ b/fs/netfs/objects.c
@@ -7,7 +7,6 @@
#include <linux/slab.h>
#include <linux/mempool.h>
-#include <linux/delay.h>
#include "internal.h"
static void netfs_free_request(struct work_struct *work);
@@ -26,17 +25,23 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping,
struct netfs_io_request *rreq;
mempool_t *mempool = ctx->ops->request_pool ?: &netfs_request_pool;
struct kmem_cache *cache = mempool->pool_data;
+ gfp_t gfp = GFP_KERNEL;
int ret;
- for (;;) {
- rreq = mempool_alloc(mempool, GFP_KERNEL);
- if (rreq)
- break;
- msleep(10);
+ /* Writeback is part of memory reclaim and must not fail due to ENOMEM. */
+ if (origin == NETFS_WRITEBACK || origin == NETFS_WRITEBACK_SINGLE) {
+ gfp = GFP_NOFS; /* Allows use of mempools. */
+
+ rreq = mempool_alloc(mempool, gfp);
+ } else {
+ rreq = mempool->alloc(gfp, mempool->pool_data);
+ if (!rreq)
+ return ERR_PTR(-ENOMEM);
}
memset(rreq, 0, kmem_cache_size(cache));
INIT_WORK(&rreq->cleanup_work, netfs_free_request);
+ rreq->gfp = gfp;
rreq->start = start;
rreq->len = len;
rreq->origin = origin;
@@ -200,13 +205,12 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq
mempool_t *mempool = rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool;
struct kmem_cache *cache = mempool->pool_data;
- for (;;) {
- subreq = mempool_alloc(rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool,
- GFP_KERNEL);
- if (subreq)
- break;
- msleep(10);
- }
+ if (rreq->gfp == GFP_KERNEL)
+ subreq = mempool->alloc(rreq->gfp, mempool->pool_data);
+ else
+ subreq = mempool_alloc(mempool, rreq->gfp);
+ if (!subreq)
+ return NULL;
memset(subreq, 0, kmem_cache_size(cache));
INIT_WORK(&subreq->work, NULL);
diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c
index a1489aa29f78..c31190993b76 100644
--- a/fs/netfs/read_pgpriv2.c
+++ b/fs/netfs/read_pgpriv2.c
@@ -53,7 +53,8 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio
trace_netfs_folio(folio, netfs_folio_trace_store_copy);
/* Attach the folio to the rolling buffer. */
- if (rolling_buffer_append(&creq->buffer, folio, 0) < 0) {
+ if (rolling_buffer_append(&creq->buffer, folio, 0, creq->gfp) < 0) {
+ folio_end_private_2(folio);
clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags);
return;
}
diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c
index a17fbf9853a4..8c0026836f9c 100644
--- a/fs/netfs/rolling_buffer.c
+++ b/fs/netfs/rolling_buffer.c
@@ -6,6 +6,7 @@
*/
#include <linux/bitops.h>
+#include <linux/mempool.h>
#include <linux/pagemap.h>
#include <linux/rolling_buffer.h>
#include <linux/slab.h>
@@ -27,7 +28,10 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp,
{
struct folio_queue *fq;
- fq = kmalloc_obj(*fq, gfp);
+ if (gfp == GFP_KERNEL)
+ fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data);
+ else
+ fq = mempool_alloc(&netfs_folioq_pool, gfp);
if (fq) {
netfs_stat(&netfs_n_folioq);
folioq_init(fq, rreq_id);
@@ -50,7 +54,7 @@ void netfs_folioq_free(struct folio_queue *folioq,
{
trace_netfs_folioq(folioq, trace);
netfs_stat_d(&netfs_n_folioq);
- kfree(folioq);
+ mempool_free(folioq, &netfs_folioq_pool);
}
EXPORT_SYMBOL(netfs_folioq_free);
@@ -60,11 +64,11 @@ EXPORT_SYMBOL(netfs_folioq_free);
* consumer.
*/
int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id,
- unsigned int direction)
+ unsigned int direction, gfp_t gfp)
{
struct folio_queue *fq;
- fq = netfs_folioq_alloc(rreq_id, GFP_NOFS, netfs_trace_folioq_rollbuf_init);
+ fq = netfs_folioq_alloc(rreq_id, gfp, netfs_trace_folioq_rollbuf_init);
if (!fq)
return -ENOMEM;
@@ -77,14 +81,14 @@ int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id,
/*
* Add another folio_queue to a rolling buffer if there's no space left.
*/
-int rolling_buffer_make_space(struct rolling_buffer *roll)
+int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp)
{
struct folio_queue *fq, *head = roll->head;
if (!folioq_full(head))
return 0;
- fq = netfs_folioq_alloc(head->rreq_id, GFP_NOFS, netfs_trace_folioq_make_space);
+ fq = netfs_folioq_alloc(head->rreq_id, gfp, netfs_trace_folioq_make_space);
if (!fq)
return -ENOMEM;
fq->prev = head;
@@ -122,7 +126,7 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll,
int nr, ix, to;
ssize_t size = 0;
- if (rolling_buffer_make_space(roll) < 0)
+ if (rolling_buffer_make_space(roll, GFP_KERNEL) < 0)
return -ENOMEM;
fq = roll->head;
@@ -153,12 +157,12 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll,
* Append a folio to the rolling buffer.
*/
ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio,
- unsigned int flags)
+ unsigned int flags, gfp_t gfp)
{
ssize_t size = folio_size(folio);
int slot;
- if (rolling_buffer_make_space(roll) < 0)
+ if (rolling_buffer_make_space(roll, gfp) < 0)
return -ENOMEM;
slot = folioq_append(roll->head, folio);
diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c
index f2761c99795a..2d9cfcd43658 100644
--- a/fs/netfs/write_issue.c
+++ b/fs/netfs/write_issue.c
@@ -108,7 +108,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
ictx = netfs_inode(wreq->inode);
if (is_cacheable)
fscache_begin_write_operation(&wreq->cache_resources, netfs_i_cookie(ictx));
- if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE) < 0)
+ if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE, wreq->gfp) < 0)
goto nomem;
wreq->cleaned_to = wreq->start;
@@ -167,7 +167,7 @@ void netfs_prepare_write(struct netfs_io_request *wreq,
*/
if (iov_iter_is_folioq(wreq_iter) &&
wreq_iter->folioq_slot >= folioq_nr_slots(wreq_iter->folioq))
- rolling_buffer_make_space(&wreq->buffer);
+ rolling_buffer_make_space(&wreq->buffer, wreq->gfp);
subreq = netfs_alloc_subrequest(wreq);
subreq->source = stream->source;
@@ -334,7 +334,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
_enter("");
- if (rolling_buffer_make_space(&wreq->buffer) < 0)
+ if (rolling_buffer_make_space(&wreq->buffer, wreq->gfp) < 0)
return -ENOMEM;
/* netfs_perform_write() may shift i_size around the page or from out
@@ -436,7 +436,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
}
/* Attach the folio to the rolling buffer. */
- rolling_buffer_append(&wreq->buffer, folio, 0);
+ rolling_buffer_append(&wreq->buffer, folio, 0, wreq->gfp);
/* Move the submission point forward to allow for write-streaming data
* not starting at the front of the page. We don't do write-streaming
@@ -720,6 +720,7 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq,
size_t iter_off = 0;
size_t fsize = folio_size(folio), flen;
loff_t fpos = folio_pos(folio);
+ ssize_t ret;
bool to_eof = false;
bool no_debug = false;
@@ -748,7 +749,11 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq,
/* Attach the folio to the rolling buffer. */
folio_get(folio);
- rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK);
+ ret = rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK, wreq->gfp);
+ if (ret < 0) {
+ folio_put(folio);
+ return ret;
+ }
/* Move the submission point forward to allow for write-streaming data
* not starting at the front of the page. We don't do write-streaming
diff --git a/fs/nfsd/nfs4xdr_gen.c b/fs/nfsd/nfs4xdr_gen.c
index a6725c773768..e5a6ea4a9349 100644
--- a/fs/nfsd/nfs4xdr_gen.c
+++ b/fs/nfsd/nfs4xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x
-// XDR specification modification time: Wed Mar 25 11:40:02 2026
+// XDR specification modification time: Tue Jun 30 11:56:05 2026
#include <linux/sunrpc/svc.h>
diff --git a/fs/nfsd/nfs4xdr_gen.h b/fs/nfsd/nfs4xdr_gen.h
index f6a458a07406..4092379a9efa 100644
--- a/fs/nfsd/nfs4xdr_gen.h
+++ b/fs/nfsd/nfs4xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */
-/* XDR specification modification time: Wed Mar 25 11:40:02 2026 */
+/* XDR specification modification time: Tue Jun 30 11:56:05 2026 */
#ifndef _LINUX_XDRGEN_NFS4_1_DECL_H
#define _LINUX_XDRGEN_NFS4_1_DECL_H
diff --git a/fs/nilfs2/Kconfig b/fs/nilfs2/Kconfig
index 7dae168e346e..0a5ace60e6ab 100644
--- a/fs/nilfs2/Kconfig
+++ b/fs/nilfs2/Kconfig
@@ -3,7 +3,7 @@ config NILFS2_FS
tristate "NILFS2 file system support"
select BUFFER_HEAD
select CRC32
- select LEGACY_DIRECT_IO
+ select FS_IOMAP
help
NILFS2 is a log-structured file system (LFS) supporting continuous
snapshotting. In addition to versioning capability of the entire
diff --git a/fs/nilfs2/Makefile b/fs/nilfs2/Makefile
index 43b60b8a4d07..516e6b85a03c 100644
--- a/fs/nilfs2/Makefile
+++ b/fs/nilfs2/Makefile
@@ -3,4 +3,4 @@ obj-$(CONFIG_NILFS2_FS) += nilfs2.o
nilfs2-y := inode.o file.o dir.o super.o namei.o page.o mdt.o \
btnode.o bmap.o btree.o direct.o dat.o recovery.o \
the_nilfs.o segbuf.o segment.o cpfile.o sufile.o \
- ifile.o alloc.o gcinode.o ioctl.o sysfs.o
+ ifile.o alloc.o gcinode.o ioctl.o sysfs.o iomap.o
diff --git a/fs/nilfs2/file.c b/fs/nilfs2/file.c
index f93b68c4877c..ad2e87c049c9 100644
--- a/fs/nilfs2/file.c
+++ b/fs/nilfs2/file.c
@@ -10,9 +10,12 @@
#include <linux/fs.h>
#include <linux/filelock.h>
#include <linux/mm.h>
+#include <linux/uio.h>
+#include <linux/iomap.h>
#include <linux/writeback.h>
#include "nilfs.h"
#include "segment.h"
+#include "iomap.h"
int nilfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
{
@@ -133,20 +136,51 @@ static int nilfs_file_mmap_prepare(struct vm_area_desc *desc)
return 0;
}
+static int nilfs_file_open(struct inode *inode, struct file *file)
+{
+ file->f_mode |= FMODE_CAN_ODIRECT;
+ return generic_file_open(inode, file);
+}
+
+static ssize_t nilfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
+{
+ if (iocb->ki_flags & IOCB_DIRECT) {
+ return iomap_dio_rw(iocb, to, &nilfs_iomap_ops,
+ NULL, 0, NULL, 0);
+ } else
+ return generic_file_read_iter(iocb, to);
+}
+
+static ssize_t nilfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+ /*
+ * NILFS2 cannot perform true direct I/O writes: new blocks are
+ * delay-allocated and are only given a real disk address when
+ * the segment constructor writes them out as part of a log,
+ * which works directly on buffer_head lists rather than
+ * through iomap. Fall back to the ordinary buffered write path
+ * for O_DIRECT writes.
+ */
+ if (iocb->ki_flags & IOCB_DIRECT)
+ iocb->ki_flags &= ~IOCB_DIRECT;
+
+ return generic_file_write_iter(iocb, from);
+}
+
/*
* We have mostly NULL's here: the current defaults are ok for
* the nilfs filesystem.
*/
const struct file_operations nilfs_file_operations = {
.llseek = generic_file_llseek,
- .read_iter = generic_file_read_iter,
- .write_iter = generic_file_write_iter,
+ .read_iter = nilfs_file_read_iter,
+ .write_iter = nilfs_file_write_iter,
.unlocked_ioctl = nilfs_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = nilfs_compat_ioctl,
#endif /* CONFIG_COMPAT */
.mmap_prepare = nilfs_file_mmap_prepare,
- .open = generic_file_open,
+ .open = nilfs_file_open,
/* .release = nilfs_release_file, */
.fsync = nilfs_sync_file,
.splice_read = filemap_splice_read,
diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c
index 51f7e125a311..f4a9d9ea9c3f 100644
--- a/fs/nilfs2/inode.c
+++ b/fs/nilfs2/inode.c
@@ -257,18 +257,6 @@ static int nilfs_write_end(const struct kiocb *iocb,
return err ? : copied;
}
-static ssize_t
-nilfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
-{
- struct inode *inode = file_inode(iocb->ki_filp);
-
- if (iov_iter_rw(iter) == WRITE)
- return 0;
-
- /* Needs synchronization with the cleaner */
- return blockdev_direct_IO(iocb, inode, iter, nilfs_get_block);
-}
-
const struct address_space_operations nilfs_aops = {
.read_folio = nilfs_read_folio,
.writepages = nilfs_writepages,
@@ -277,7 +265,6 @@ const struct address_space_operations nilfs_aops = {
.write_begin = nilfs_write_begin,
.write_end = nilfs_write_end,
.invalidate_folio = block_invalidate_folio,
- .direct_IO = nilfs_direct_IO,
.migrate_folio = buffer_migrate_folio_norefs,
.is_partially_uptodate = block_is_partially_uptodate,
};
diff --git a/fs/nilfs2/iomap.c b/fs/nilfs2/iomap.c
new file mode 100644
index 000000000000..3ae3bf6ed368
--- /dev/null
+++ b/fs/nilfs2/iomap.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NILFS iomap support implementation.
+ *
+ * Written by Viacheslav Dubeyko.
+ */
+
+#include <linux/iomap.h>
+#include <linux/pagemap.h>
+#include "nilfs.h"
+#include "mdt.h"
+#include "iomap.h"
+
+static int nilfs_iomap_begin(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, struct iomap *srcmap)
+{
+ struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
+ struct nilfs_inode_info *ii = NILFS_I(inode);
+ sector_t blkoff = offset >> inode->i_blkbits;
+ unsigned int maxblocks;
+ __u64 blknum = 0;
+ int ret;
+
+ /* Completely beyond EOF. Treat as hole */
+ if (i_size_read(inode) <= offset) {
+ iomap->type = IOMAP_HOLE;
+ iomap->addr = IOMAP_NULL_ADDR;
+ iomap->offset = offset;
+ iomap->length = length;
+ return 0;
+ }
+
+ /* Clamp length if the requested range goes beyond i_size */
+ if (offset + length > i_size_read(inode)) {
+ loff_t i_size = i_size_read(inode);
+ unsigned int blocksize = i_blocksize(inode);
+
+ length = round_up(i_size, blocksize) - offset;
+ }
+
+ maxblocks = min_t(loff_t, length >> inode->i_blkbits, INT_MAX);
+ if (maxblocks == 0)
+ maxblocks = 1;
+
+ down_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+ ret = nilfs_bmap_lookup_contig(ii->i_bmap, blkoff, &blknum, maxblocks);
+ up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem);
+
+ if (ret == -ENOENT) {
+ iomap->type = IOMAP_HOLE;
+ iomap->addr = IOMAP_NULL_ADDR;
+ iomap->offset = offset;
+ iomap->length = min_t(loff_t, length, i_blocksize(inode));
+ return 0;
+ } else if (ret < 0)
+ return ret;
+
+ iomap->bdev = inode->i_sb->s_bdev;
+ iomap->offset = offset;
+ iomap->length = min_t(loff_t, length, (loff_t)ret << inode->i_blkbits);
+ iomap->addr = (loff_t)blknum << inode->i_blkbits;
+ iomap->type = IOMAP_MAPPED;
+ iomap->flags = IOMAP_F_MERGED;
+
+ return 0;
+}
+
+const struct iomap_ops nilfs_iomap_ops = {
+ .iomap_begin = nilfs_iomap_begin,
+};
diff --git a/fs/nilfs2/iomap.h b/fs/nilfs2/iomap.h
new file mode 100644
index 000000000000..adef3e22346d
--- /dev/null
+++ b/fs/nilfs2/iomap.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * NILFS iomap support declarations.
+ *
+ * Written by Viacheslav Dubeyko.
+ */
+
+#ifndef _NILFS_IOMAP_H
+#define _NILFS_IOMAP_H
+
+extern const struct iomap_ops nilfs_iomap_ops;
+
+#endif /* _NILFS_IOMAP_H */
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index 4fb10d51211c..fc6cec7ce130 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -406,37 +406,35 @@ out:
* Check for the presence of an EA "$LXDEV" (used by WSL)
* and return its value as a device address
*/
-int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags)
+int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags,
+ bool *has_lxmod)
{
int err;
__le32 v;
+ *has_lxmod = false;
+
if (!(flags & NTFS_VOL_UID)) {
/* Load uid to lxuid EA */
err = ntfs_get_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v,
sizeof(v));
- if (err < 0)
- return err;
- if (err != sizeof(v))
- return -EIO;
- i_uid_write(inode, le32_to_cpu(v));
+ if (err == sizeof(v))
+ i_uid_write(inode, le32_to_cpu(v));
}
if (!(flags & NTFS_VOL_GID)) {
/* Load gid to lxgid EA */
err = ntfs_get_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v,
sizeof(v));
- if (err < 0)
- return err;
- if (err != sizeof(v))
- return -EIO;
- i_gid_write(inode, le32_to_cpu(v));
+ if (err == sizeof(v))
+ i_gid_write(inode, le32_to_cpu(v));
}
/* Load mode to lxmod EA */
err = ntfs_get_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, sizeof(v));
if (err == sizeof(v)) {
inode->i_mode = le32_to_cpu(v);
+ *has_lxmod = true;
} else {
/* Everyone gets all permissions. */
inode->i_mode |= 0777;
@@ -810,6 +808,12 @@ err_out:
return err;
}
+static bool ntfs_is_reserved_lxattr(const char *name)
+{
+ return !strcmp(name, "$LXUID") || !strcmp(name, "$LXGID") ||
+ !strcmp(name, "$LXMOD") || !strcmp(name, "$LXDEV");
+}
+
static int ntfs_setxattr(const struct xattr_handler *handler,
struct mnt_idmap *idmap, struct dentry *unused,
struct inode *inode, const char *name, const void *value,
@@ -822,6 +826,9 @@ static int ntfs_setxattr(const struct xattr_handler *handler,
if (NVolShutdown(ni->vol))
return -EIO;
+ if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
if (!strcmp(name, SYSTEM_DOS_ATTRIB)) {
if (sizeof(u8) != size) {
err = -EINVAL;
diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h
index 1f63bd55e057..acb39c2a6fbc 100644
--- a/fs/ntfs/ea.h
+++ b/fs/ntfs/ea.h
@@ -10,7 +10,8 @@
extern const struct xattr_handler *const ntfs_xattr_handlers[];
int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t dev);
-int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags);
+int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags,
+ bool *has_lxmod);
int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size,
unsigned int flags);
ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index a6855d62e1fc..d4282822b3ce 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -348,11 +348,6 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (err)
goto out;
- /* Apply mount masks after saving the unmasked POSIX mode. */
- if (S_ISDIR(vi->i_mode))
- vi->i_mode &= ~vol->dmask;
- else
- vi->i_mode &= ~vol->fmask;
}
mark_inode_dirty(vi);
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 50e244aa372e..39c7fd8c1149 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -682,6 +682,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
unsigned int name_len = 4, flags = 0;
int extend_sys = 0;
dev_t dev = 0;
+ bool has_lxmod = false;
bool vol_err = true;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@@ -862,7 +863,7 @@ skip_attr_list_load:
err = ntfs_attr_lookup(AT_EA_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx);
if (!err) {
NInoSetHasEA(ni);
- ntfs_ea_get_wsl_inode(vi, &dev, flags);
+ ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod);
}
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
@@ -886,16 +887,18 @@ skip_attr_list_load:
if (S_ISDIR(vi->i_mode)) {
/*
- * Apply the directory permissions mask set in the mount
- * options.
+ * Apply the directory permissions mask set in the mount options
+ * when no per-file WSL mode is present.
*/
- vi->i_mode &= ~vol->dmask;
+ if (!has_lxmod)
+ vi->i_mode &= ~vol->dmask;
/* Things break without this kludge! */
if (vi->i_nlink > 1)
set_nlink(vi, 1);
} else {
- /* Apply the file permissions mask set in the mount options. */
- vi->i_mode &= ~vol->fmask;
+ /* Apply the file permissions mask when no WSL mode is present. */
+ if (!has_lxmod)
+ vi->i_mode &= ~vol->fmask;
}
/*
diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c
index 835a041023a2..aa2e017a4384 100644
--- a/fs/ntfs/lcnalloc.c
+++ b/fs/ntfs/lcnalloc.c
@@ -298,7 +298,12 @@ struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 st
clusters = count;
rlpos = rlsize = 0;
mapping = lcnbmp_vi->i_mapping;
- i_size = i_size_read(lcnbmp_vi);
+ /*
+ * lcn_empty_bits_per_page is sized from nr_clusters, but $Bitmap can
+ * cover more clusters than that; bound the scan by the array.
+ */
+ i_size = min_t(s64, i_size_read(lcnbmp_vi),
+ ((s64)vol->nr_clusters + 7) >> 3);
while (1) {
ntfs_debug("Start of outer while loop: done_zones 0x%x, search_zone %i, pass %i, zone_start 0x%llx, zone_end 0x%llx, bmp_initial_pos 0x%llx, bmp_pos 0x%llx, rlpos %i, rlsize %i.",
done_zones, search_zone, pass,
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 0de5b63ef407..7091b2496fac 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -424,8 +424,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
* directories, also setup the index values to the defaults.
*/
if (S_ISDIR(mode)) {
- mode &= ~vol->dmask;
-
NInoSetMstProtected(ni);
ni->itype.index.block_size = 4096;
ni->itype.index.block_size_bits = ntfs_ffs(4096) - 1;
@@ -439,8 +437,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
ni->itype.index.vcn_size_bits =
vol->sector_size_bits;
}
- } else {
- mode &= ~vol->fmask;
}
if (IS_RDONLY(vi))
diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c
index ced4c675d91f..0d3988992119 100644
--- a/fs/ntfs/reparse.c
+++ b/fs/ntfs/reparse.c
@@ -892,12 +892,7 @@ int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len);
if (!err) {
- int len = strlen(sub_name);
-
- for (i = 0; i < len; i++) {
- if (sub_name[i] == '\\')
- sub_name[i] = '/';
- }
+ strreplace(sub_name, '\\', '/');
ni->target = sub_name;
sub_name = NULL;
if (prt_sub_shared)
diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c
index 8e0fd400e7f7..17eb275a21ff 100644
--- a/fs/ntfs/runlist.c
+++ b/fs/ntfs/runlist.c
@@ -884,6 +884,13 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
ntfs_error(vol->sb, "lcn == -1");
}
#endif
+ /* Check lcn is within the volume. */
+ if (unlikely(lcn >= (s64)vol->nr_clusters)) {
+ ntfs_error(vol->sb,
+ "LCN >= nr_clusters in mapping pairs array.");
+ goto err_out;
+ }
+
/* Check lcn is not below -1. */
if (unlikely(lcn < -1)) {
ntfs_error(vol->sb, "Invalid s64 < -1 in mapping pairs array.");
diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index 3628a737d7cc..b1c315206ffa 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -278,7 +278,7 @@ int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr,
next = Add2Ptr(attr, asize);
aoff = PtrOffset(rec, attr);
rsize = le32_to_cpu(attr->res.data_size);
- is_data = attr->type == ATTR_DATA && !attr->name_len;
+ is_data = attr->type == ATTR_DATA;
/* len - how many clusters required to store 'rsize' bytes */
if (is_attr_compressed(attr)) {
@@ -433,6 +433,7 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
struct ATTRIB **ret, bool no_da)
{
int err = 0;
+ struct ntfs_inode *nb = ni->base;
struct ntfs_sb_info *sbi = ni->mi.sbi;
u8 cluster_bits = sbi->cluster_bits;
bool is_mft = ni->mi.rno == MFT_REC_MFT && type == ATTR_DATA &&
@@ -703,8 +704,8 @@ pack_runs:
goto again;
}
- if (!ni->attr_list.size) {
- err = ni_create_attr_list(ni);
+ if (!nb->attr_list.size) {
+ err = ni_create_attr_list(nb);
/* In case of error layout of records is not changed. */
if (err)
goto undo_2;
@@ -877,8 +878,7 @@ ok1:
if (ret)
*ret = attr_b;
- if (((type == ATTR_DATA && !name_len) ||
- (type == ATTR_ALLOC && name == I30_NAME))) {
+ if ((type == ATTR_DATA || (type == ATTR_ALLOC && name == I30_NAME))) {
/* Update inode_set_bytes. */
if (attr_b->non_res &&
inode_get_bytes(&ni->vfs_inode) != new_alloc) {
@@ -1001,7 +1001,6 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
struct ATTRIB *attr, *attr_b;
struct ATTR_LIST_ENTRY *le, *le_b;
struct mft_inode *mi, *mi_b;
- struct page *page;
CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0;
CLST alloc, evcn;
unsigned fr;
@@ -1026,7 +1025,8 @@ again:
step = 0;
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b) {
err = -ENOENT;
goto out;
@@ -1036,11 +1036,15 @@ again:
u32 data_size = le32_to_cpu(attr_b->res.data_size);
*lcn = RESIDENT_LCN;
*len = data_size;
- if (res && data_size) {
- page = alloc_page(GFP_KERNEL);
- if (!page) {
- err = -ENOMEM;
- } else {
+ if (res) {
+ *res = NULL;
+ if (data_size) {
+ struct page *page = alloc_page(GFP_KERNEL);
+ if (!page) {
+ err = -ENOMEM;
+ goto out;
+ }
+
*res = page_address(page);
memcpy(*res, resident_data(attr_b), data_size);
}
@@ -1067,7 +1071,8 @@ again:
mi = mi_b;
if (le_b && (vcn < svcn || evcn1 <= vcn)) {
- attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+ attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, &vcn,
&mi);
if (!attr) {
err = -EINVAL;
@@ -1140,8 +1145,9 @@ again:
if (vcn < svcn || evcn1 <= vcn) {
struct ATTRIB *attr2;
/* Load runs for truncated vcn. */
- attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
- 0, &vcn, &mi);
+ attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA,
+ ni->file.ads.name,
+ ni->file.ads.len, &vcn, &mi);
if (!attr2) {
err = -EINVAL;
goto out;
@@ -1155,8 +1161,9 @@ again:
if (vcn0 < svcn || evcn1 <= vcn0) {
struct ATTRIB *attr2;
- attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
- 0, &vcn0, &mi);
+ attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA,
+ ni->file.ads.name,
+ ni->file.ads.len, &vcn0, &mi);
if (!attr2) {
err = -EINVAL;
goto out;
@@ -1269,8 +1276,9 @@ repack:
goto undo1;
/* Layout of records is changed. */
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
- 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+ ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b) {
err = -ENOENT;
goto out;
@@ -1300,7 +1308,8 @@ repack:
svcn = evcn1;
/* Estimate next attribute. */
- attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);
+ attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &svcn, &mi);
if (!attr) {
/* Insert new attribute segment. */
@@ -1333,7 +1342,8 @@ repack:
goto out;
}
- attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0, &le->id);
+ attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &le->id);
if (!attr) {
err = -EINVAL;
goto out;
@@ -1362,9 +1372,10 @@ repack:
ins_ext:
if (evcn1 > next_svcn) {
- err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
- next_svcn, evcn1 - next_svcn,
- attr_b->flags, &attr, &mi, NULL);
+ err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run, next_svcn,
+ evcn1 - next_svcn, attr_b->flags,
+ &attr, &mi, NULL);
if (err)
goto out;
}
@@ -1398,7 +1409,8 @@ int attr_data_write_resident(struct ntfs_inode *ni, struct folio *folio)
struct ATTRIB *attr;
u32 data_size;
- attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
+ attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi);
if (!attr)
return -EINVAL;
@@ -1775,7 +1787,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
u64 total_size, valid_size, data_size;
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b)
return -ENOENT;
@@ -1798,7 +1811,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
goto out;
} else {
le = le_b;
- attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+ attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, &vcn,
&mi);
if (!attr) {
err = -EINVAL;
@@ -1885,8 +1899,9 @@ repack:
goto out;
/* Layout of records is changed. */
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
- 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+ ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b) {
err = -ENOENT;
goto out;
@@ -1902,7 +1917,8 @@ repack:
svcn = evcn1;
/* Estimate next attribute. */
- attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);
+ attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &svcn, &mi);
if (attr) {
CLST alloc = bytes_to_cluster(
@@ -1931,7 +1947,8 @@ repack:
goto out;
}
- attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0,
+ attr = mi_find_attr(ni, mi, NULL, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len,
&le->id);
if (!attr) {
err = -EINVAL;
@@ -1962,9 +1979,10 @@ repack:
}
ins_ext:
if (evcn1 > next_svcn) {
- err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
- next_svcn, evcn1 - next_svcn,
- attr_b->flags, &attr, &mi, NULL);
+ err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run, next_svcn,
+ evcn1 - next_svcn, attr_b->flags,
+ &attr, &mi, NULL);
if (err)
goto out;
}
@@ -2007,7 +2025,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
return 0;
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b)
return -ENOENT;
@@ -2037,7 +2056,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
/* Simple truncate file at 'vbo'. */
truncate_setsize(&ni->vfs_inode, vbo);
- err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, vbo,
+ err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &ni->file.run, vbo,
&valid_size, true);
if (!err && valid_size < ni->i_valid)
@@ -2061,7 +2081,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
/*
* The requested range is full in delayed clusters.
*/
- err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, run,
+ err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run,
i_size - bytes, NULL, false,
NULL, true);
goto out;
@@ -2074,7 +2095,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
/* Layout of records maybe changed. */
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, NULL,
&mi_b);
if (!attr_b || !attr_b->non_res) {
err = -ENOENT;
@@ -2105,7 +2127,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
}
le = le_b;
- attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, &mi);
+ attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &vcn, &mi);
if (!attr) {
err = -EINVAL;
goto out;
@@ -2169,7 +2192,8 @@ check_seg:
next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
if (next_svcn + eat + done < evcn1) {
err = ni_insert_nonresident(
- ni, ATTR_DATA, NULL, 0, run, next_svcn,
+ ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run, next_svcn,
evcn1 - eat - next_svcn, a_flags, &attr,
&mi, &le);
if (err)
@@ -2209,7 +2233,8 @@ check_seg:
/* Look for required attribute. */
attr = mi_find_attr(ni, mi, NULL, ATTR_DATA,
- NULL, 0, &le->id);
+ ni->file.ads.name,
+ ni->file.ads.len, &le->id);
if (!attr) {
err = -EINVAL;
goto out;
@@ -2232,7 +2257,8 @@ next_attr:
if (!attr_b) {
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, NULL,
&mi_b);
if (!attr_b) {
err = -ENOENT;
@@ -2293,7 +2319,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
return 0;
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b)
return -ENOENT;
@@ -2364,7 +2391,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
goto bad_inode;
} else {
le = le_b;
- attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+ attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, &vcn,
&mi);
if (!attr) {
err = -EINVAL;
@@ -2416,10 +2444,10 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
if (next_svcn < evcn1) {
/* Insert new attribute segment. */
- err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
- next_svcn,
- evcn1 - next_svcn, a_flags,
- &attr, &mi, &le);
+ err = ni_insert_nonresident(
+ ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run, next_svcn,
+ evcn1 - next_svcn, a_flags, &attr, &mi, &le);
if (err)
goto undo_punch;
@@ -2454,7 +2482,8 @@ done:
goto out;
if (!attr_b) {
- attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL,
+ attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, NULL,
&mi_b);
if (!attr_b) {
err = -EINVAL;
@@ -2512,7 +2541,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
return 0;
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi_b);
if (!attr_b)
return -ENOENT;
@@ -2559,11 +2589,13 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
down_write(&ni->file.run_lock);
if (!attr_b->non_res) {
- err = attr_set_size(ni, ATTR_DATA, NULL, 0, run,
- data_size + bytes, NULL, false);
+ err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run, data_size + bytes,
+ NULL, false);
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, NULL,
&mi_b);
if (!attr_b) {
err = -EINVAL;
@@ -2604,7 +2636,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
goto bad_inode;
} else {
le = le_b;
- attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+ attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, &vcn,
&mi);
if (!attr) {
err = -EINVAL;
@@ -2647,12 +2680,14 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
}
if (next_svcn < evcn1 + len) {
- err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
- next_svcn, evcn1 + len - next_svcn,
- a_flags, NULL, NULL, NULL);
+ err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, run, next_svcn,
+ evcn1 + len - next_svcn, a_flags,
+ NULL, NULL, NULL);
le_b = NULL;
- attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+ attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, NULL,
&mi_b);
if (!attr_b) {
err = -EINVAL;
@@ -2709,7 +2744,8 @@ undo_insert_range:
goto bad_inode;
} else {
le = le_b;
- attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+ attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len, &vcn,
&mi);
if (!attr) {
goto bad_inode;
@@ -2754,7 +2790,8 @@ int attr_force_nonresident(struct ntfs_inode *ni)
struct ATTR_LIST_ENTRY *le = NULL;
struct mft_inode *mi;
- attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi);
+ attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, NULL, &mi);
if (!attr) {
_ntfs_bad_inode(&ni->vfs_inode);
return -ENOENT;
diff --git a/fs/ntfs3/attrlist.c b/fs/ntfs3/attrlist.c
index 270a29323530..8710560cf294 100644
--- a/fs/ntfs3/attrlist.c
+++ b/fs/ntfs3/attrlist.c
@@ -19,6 +19,7 @@
static inline bool al_is_valid_le(const struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *le)
{
+ ni = ni->base;
if (!le || !ni->attr_list.le || !ni->attr_list.size)
return false;
@@ -28,6 +29,7 @@ static inline bool al_is_valid_le(const struct ntfs_inode *ni,
void al_destroy(struct ntfs_inode *ni)
{
+ ni = ni->base;
run_close(&ni->attr_list.run);
kvfree(ni->attr_list.le);
ni->attr_list.le = NULL;
@@ -47,6 +49,7 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
size_t lsize;
void *le = NULL;
+ ni = ni->base;
if (ni->attr_list.size)
return 0;
@@ -199,6 +202,7 @@ struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *ret = NULL;
u32 type_in = le32_to_cpu(type);
+ ni = ni->base;
while ((le = al_enumerate(ni, le))) {
u64 le_vcn;
int diff = le32_to_cpu(le->type) - type_in;
@@ -256,6 +260,7 @@ static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni,
struct ATTR_LIST_ENTRY *le = NULL, *prev;
u32 type_in = le32_to_cpu(type);
+ ni = ni->base;
/* List entries are sorted by type, name and VCN. */
while ((le = al_enumerate(ni, prev = le))) {
int diff = le32_to_cpu(le->type) - type_in;
@@ -305,6 +310,7 @@ int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
u64 new_size;
typeof(ni->attr_list) *al = &ni->attr_list;
+ ni = ni->base;
/*
* Compute the size of the new 'le'
*/
@@ -374,8 +380,10 @@ bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
{
u16 size;
size_t off;
- typeof(ni->attr_list) *al = &ni->attr_list;
+ typeof(ni->attr_list) *al;
+ ni = ni->base;
+ al = &ni->attr_list;
if (!al_is_valid_le(ni, le))
return false;
@@ -395,7 +403,10 @@ int al_update(struct ntfs_inode *ni, int sync)
{
int err;
struct ATTRIB *attr;
- typeof(ni->attr_list) *al = &ni->attr_list;
+ typeof(ni->attr_list) *al;
+
+ ni = ni->base;
+ al = &ni->attr_list;
if (!al->dirty || !al->size)
return 0;
diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index 6f8ad8428f49..eb9152e9fa22 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -184,7 +184,7 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
struct cpu_str *uni, u32 max_ulen,
enum utf16_endian endian)
{
- int ret, slen;
+ int ret, slen, i;
const u8 *end;
struct nls_table *nls = sbi->options->nls;
u16 *uname = uni->name;
@@ -194,50 +194,83 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
if (!nls) {
/* utf8 -> utf16 */
ret = _utf8s_to_utf16s(name, name_len, endian, uname, max_ulen);
- uni->len = ret;
- return ret;
- }
-
- for (ret = 0, end = name + name_len; name < end; ret++, name += slen) {
- if (ret >= max_ulen)
- return -ENAMETOOLONG;
+ } else {
+ for (ret = 0, end = name + name_len; name < end;
+ ret++, name += slen) {
+ if (ret >= max_ulen)
+ return -ENAMETOOLONG;
- slen = nls->char2uni(name, end - name, uname + ret);
- if (!slen)
- return -EINVAL;
- if (slen < 0)
- return slen;
- }
+ slen = nls->char2uni(name, end - name, uname + ret);
+ if (!slen)
+ return -EINVAL;
+ if (slen < 0)
+ return slen;
+ }
#ifdef __BIG_ENDIAN
- if (endian == UTF16_LITTLE_ENDIAN) {
- int i = ret;
+ if (endian == UTF16_LITTLE_ENDIAN) {
+ i = ret;
- while (i--) {
- __cpu_to_le16s(uname);
- uname++;
+ while (i--) {
+ __cpu_to_le16s(uname);
+ uname++;
+ }
}
- }
#else
- if (endian == UTF16_BIG_ENDIAN) {
- int i = ret;
+ if (endian == UTF16_BIG_ENDIAN) {
+ i = ret;
- while (i--) {
- __cpu_to_be16s(uname);
- uname++;
+ while (i--) {
+ __cpu_to_be16s(uname);
+ uname++;
+ }
}
- }
#endif
+ }
uni->len = ret;
+ uni->ads_len = 0;
+ if (ret > 0 && sbi->options->ads) {
+ uname = uni->name;
+ /* Find delimiter in range [1 : ret-2). */
+ for (i = 1; i + 1 < ret; i++) {
+ if (uname[i] == ':') {
+ uni->ads_len = ret - i - 1;
+ uni->len = i;
+ uname[i] = 0;
+ ret = i;
+
+ uname += i + 1;
+ i = uni->ads_len;
+ /* Return ADS name as little endian. Always */
+#ifdef __BIG_ENDIAN
+ if (endian == UTF16_LITTLE_ENDIAN) {
+ while (i--) {
+ __cpu_to_le16s(uname);
+ uname++;
+ }
+ }
+#else
+ if (endian == UTF16_BIG_ENDIAN) {
+ while (i--) {
+ __cpu_to_be16s(uname);
+ uname++;
+ }
+ }
+#endif
+ break;
+ }
+ }
+ }
+
return ret;
}
/*
* dir_search_u - Helper function.
*/
-struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
- struct ntfs_fnd *fnd)
+struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni,
+ struct ntfs_fnd *fnd, u32 flags)
{
int err = 0;
struct super_block *sb = dir->i_sb;
@@ -267,7 +300,7 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
goto out;
}
- inode = ntfs_iget5(sb, &e->ref, uni);
+ inode = ntfs_iget5_flags(sb, &e->ref, uni, flags);
if (!IS_ERR(inode) && is_bad_inode(inode)) {
iput(inode);
err = -EINVAL;
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index fa8e2e56ff3f..2abf334bfa0c 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -753,7 +753,9 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
setattr_copy(idmap, inode, attr);
- if (mode != inode->i_mode) {
+ if (!is_ni_base(ni)) {
+ ia_valid &= ~ATTR_SIZE;
+ } else if (mode != inode->i_mode) {
err = ntfs_acl_chmod(idmap, dentry);
if (err)
goto out;
@@ -820,15 +822,31 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
size_t bytes = iov_iter_count(iter);
loff_t valid, i_size, vbo, end;
unsigned int dio_flags;
- ssize_t err;
+ ssize_t ret;
- err = check_read_restriction(inode);
- if (err)
- return err;
+ ret = check_read_restriction(inode);
+ if (ret)
+ return ret;
if (!bytes)
return 0; /* skip atime */
+ if (ni->file.ads.len == ARRAY_SIZE(QUERY_STREAMS) &&
+ !memcmp(ni->file.ads.name, QUERY_STREAMS, sizeof(QUERY_STREAMS))) {
+ /* Query ADS. */
+ if (unlikely(iocb->ki_flags & IOCB_DIRECT)) {
+ ntfs_inode_warn(
+ inode,
+ "direct I/O for streams is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ inode_lock_shared(inode);
+ ret = ni_query_ads(ni, &iocb->ki_pos, iter);
+ inode_unlock_shared(inode);
+ return ret;
+ }
+
if (is_compressed(ni)) {
if (iocb->ki_flags & IOCB_DIRECT) {
ntfs_inode_warn(
@@ -867,17 +885,17 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
if (ni->file.run_da.count) {
/* Direct I/O is not compatible with delalloc. */
- err = ni_allocate_da_blocks(ni);
- if (err)
+ ret = ni_allocate_da_blocks(ni);
+ if (ret)
goto out;
}
- err = iomap_dio_rw(iocb, iter, &ntfs_iomap_ops, NULL, dio_flags,
+ ret = iomap_dio_rw(iocb, iter, &ntfs_iomap_ops, NULL, dio_flags,
NULL, 0);
- if (err <= 0)
+ if (ret <= 0)
goto out;
- end = vbo + err;
+ end = vbo + ret;
if (valid < end) {
size_t to_zero = end - valid;
/* Fix iter. */
@@ -889,35 +907,36 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
bytes = i_size - vbo;
iov_iter_zero(bytes, iter);
iocb->ki_pos += bytes;
- err = bytes;
+ ret = bytes;
}
out:
inode_unlock_shared(inode);
file_accessed(file);
- return err;
+ return ret;
}
/*
* ntfs_file_splice_read - file_operations::splice_read
*/
-static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
+static ssize_t ntfs_file_splice_read(struct file *file, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len,
unsigned int flags)
{
- struct inode *inode = file_inode(in);
- ssize_t err;
+ struct inode *inode = file_inode(file);
+ struct ntfs_inode *ni = ntfs_i(inode);
+ ssize_t ret;
- err = check_read_restriction(inode);
- if (err)
- return err;
+ ret = check_read_restriction(inode);
+ if (ret)
+ return ret;
- if (is_compressed(ntfs_i(inode))) {
+ if (is_compressed(ni)) {
/* Turn off readahead for compressed files. */
- in->f_ra.ra_pages = 0;
+ file->f_ra.ra_pages = 0;
}
- return filemap_splice_read(in, ppos, pipe, len, flags);
+ return filemap_splice_read(file, ppos, pipe, len, flags);
}
/*
@@ -1420,7 +1439,8 @@ static int ntfs_file_release(struct inode *inode, struct file *file)
down_write(&ni->file.run_lock);
/* Deallocate preallocated. */
- err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run,
+ err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &ni->file.run,
inode->i_size, &ni->i_valid, false, NULL,
true);
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 3ad309d38fe8..bead01a953f3 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -132,6 +132,13 @@ void ni_clear(struct ntfs_inode *ni)
ni->file.offs_folio = NULL;
}
#endif
+ kfree(ni->file.ads.name);
+ ni->file.ads.name = NULL;
+ }
+
+ if (ni->base && ni->base != ni) {
+ iput(&ni->base->vfs_inode);
+ ni->base = NULL;
}
mi_clear(&ni->mi);
@@ -145,6 +152,7 @@ int ni_load_mi_ex(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
int err;
struct mft_inode *r;
+ ni = ni->base;
r = ni_find_mi(ni, rno);
if (r)
goto out;
@@ -171,6 +179,7 @@ int ni_load_mi(struct ntfs_inode *ni, const struct ATTR_LIST_ENTRY *le,
{
u64 rno;
+ ni = ni->base;
if (!le) {
*mi = &ni->mi;
return 0;
@@ -197,6 +206,7 @@ struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr,
struct ATTR_LIST_ENTRY *le;
struct mft_inode *m;
+ ni = ni->base;
if (!ni->attr_list.size ||
(!name_len && (type == ATTR_LIST || type == ATTR_STD))) {
if (le_o)
@@ -257,6 +267,7 @@ struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
struct mft_inode *mi2;
struct ATTR_LIST_ENTRY *le2;
+ ni = ni->base;
/* Do we have an attribute list? */
if (!ni->attr_list.size) {
*le = NULL;
@@ -290,6 +301,7 @@ int ni_load_all_mi(struct ntfs_inode *ni)
int err;
struct ATTR_LIST_ENTRY *le;
+ ni = ni->base;
if (!ni->attr_list.size)
return 0;
@@ -316,6 +328,7 @@ bool ni_add_subrecord(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
{
struct mft_inode *m;
+ ni = ni->base;
m = kzalloc_obj(struct mft_inode, GFP_NOFS);
if (!m)
return false;
@@ -348,6 +361,7 @@ int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
u32 type_in;
int diff;
+ ni = ni->base;
if (base_only || type == ATTR_LIST || !ni->attr_list.size) {
attr = mi_find_attr(ni, &ni->mi, NULL, type, name, name_len,
id);
@@ -417,6 +431,7 @@ ni_ins_new_attr(struct ntfs_inode *ni, struct mft_inode *mi,
bool le_added = false;
struct MFT_REF ref;
+ ni = ni->base;
mi_get_ref(mi, &ref);
if (type != ATTR_LIST && !le && ni->attr_list.size) {
@@ -618,6 +633,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
struct MFT_REC *mrec;
__le16 id;
+ ni = ni->base;
if (!ni->attr_list.dirty)
return 0;
@@ -763,6 +779,7 @@ int ni_create_attr_list(struct ntfs_inode *ni)
u32 free_b, nb, to_free, rs;
u16 sz;
+ ni = ni->base;
is_mft = ni->mi.rno == MFT_REC_MFT;
rec = ni->mi.mrec;
rs = sbi->record_size;
@@ -917,6 +934,7 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
bool is_mft, is_mft_data;
struct ntfs_sb_info *sbi = ni->mi.sbi;
+ ni = ni->base;
is_mft = ni->mi.rno == MFT_REC_MFT;
is_mft_data = is_mft && type == ATTR_DATA && !name_len;
@@ -1069,6 +1087,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
__le16 id;
u16 t16;
+ ni = ni->base;
is_mft = ni->mi.rno == MFT_REC_MFT;
rec = ni->mi.mrec;
@@ -1215,6 +1234,7 @@ static int ni_expand_mft_list(struct ntfs_inode *ni)
struct mft_inode *mi, *mi_min, *mi_new;
struct ntfs_sb_info *sbi = ni->mi.sbi;
+ ni = ni->base;
/* Find the nearest MFT. */
mft_min = 0;
mft_new = 0;
@@ -1347,6 +1367,7 @@ int ni_expand_list(struct ntfs_inode *ni)
bool is_mft = ni->mi.rno == MFT_REC_MFT;
struct MFT_REF ref;
+ ni = ni->base;
mi_get_ref(&ni->mi, &ref);
le = NULL;
@@ -1419,6 +1440,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
u32 run_size, asize;
struct ntfs_sb_info *sbi = ni->mi.sbi;
+ ni = ni->base;
/* Estimate packed size (run_buf=NULL). */
err = run_pack(run, svcn, len, NULL, sbi->max_bytes_per_attr - run_off,
&plen);
@@ -1488,6 +1510,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
u32 asize = SIZEOF_RESIDENT + name_size + ALIGN(data_size, 8);
struct ATTRIB *attr;
+ ni = ni->base;
err = ni_insert_attr(ni, type, name, name_len, asize, SIZEOF_RESIDENT,
0, &attr, mi, le);
if (err)
@@ -1519,6 +1542,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
void ni_remove_attr_le(struct ntfs_inode *ni, struct ATTRIB *attr,
struct mft_inode *mi, struct ATTR_LIST_ENTRY *le)
{
+ ni = ni->base;
mi_remove_attr(ni, mi, attr);
if (le)
@@ -1543,6 +1567,7 @@ int ni_delete_all(struct ntfs_inode *ni)
bool nt3 = is_ntfs3(sbi);
struct MFT_REF ref;
+ ni = ni->base;
while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) {
if (!nt3 || attr->name_len) {
;
@@ -1620,6 +1645,7 @@ struct ATTR_FILE_NAME *ni_fname_name(struct ntfs_inode *ni,
struct ATTRIB *attr = NULL;
struct ATTR_FILE_NAME *fname;
+ ni = ni->base;
if (le)
*le = NULL;
@@ -1661,6 +1687,7 @@ struct ATTR_FILE_NAME *ni_fname_type(struct ntfs_inode *ni, u8 name_type,
struct ATTR_FILE_NAME *fname;
*le = NULL;
+ ni = ni->base;
if (name_type == FILE_NAME_POSIX)
return NULL;
@@ -1691,6 +1718,7 @@ int ni_new_attr_flags(struct ntfs_inode *ni, enum FILE_ATTRIBUTE new_fa)
__le16 new_aflags;
u32 new_asize;
+ ni = ni->base;
attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
if (!attr)
return -EINVAL;
@@ -1772,6 +1800,7 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
u16 len;
typeof(rp->CompressReparseBuffer) *cmpr;
+ ni = ni->base;
/* Try to estimate reparse point. */
if (!attr->non_res) {
rp = resident_data_ex(attr, sizeof(struct REPARSE_DATA_BUFFER));
@@ -2000,6 +2029,7 @@ int ni_decompress_file(struct ntfs_inode *ni)
struct mft_inode *mi;
int err;
+ ni = ni->base;
/* Clusters for decompressed data. */
cend = bytes_to_cluster(sbi, i_size);
@@ -2261,6 +2291,7 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages,
struct ATTRIB *attr;
CLST frame, clst_data;
+ ni = ni->base;
/*
* To simplify decompress algorithm do vmap for source
* and target pages.
@@ -2455,6 +2486,15 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages,
err = unc_size;
else if (!unc_size || unc_size > frame_size)
err = -EINVAL;
+ else if (unc_size < frame_size) {
+ /*
+ * Partial decompress: zero the [unc_size, frame_size)
+ * tail. decompress_lznt() leaves it untouched, so
+ * without this the freshly vmapped pages would expose
+ * uninitialized kernel memory to userspace.
+ */
+ memset(frame_mem + unc_size, 0, frame_size - unc_size);
+ }
}
if (!err && valid_size < frame_vbo + frame_size) {
size_t ok = valid_size - frame_vbo;
@@ -2510,6 +2550,7 @@ int ni_write_frame(struct ntfs_inode *ni, struct page **pages,
size_t compr_size, ondisk_size;
struct lznt *lznt;
+ ni = ni->base;
attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi);
if (!attr) {
err = -ENOENT;
@@ -2635,6 +2676,7 @@ int ni_remove_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
u16 de_key_size = le16_to_cpu(de->key_size);
u8 name_type;
+ ni = ni->base;
*undo_step = 0;
/* Find name in record. */
@@ -2697,6 +2739,7 @@ bool ni_remove_name_undo(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
struct ATTRIB *attr;
u16 de_key_size;
+ ni = ni->base;
switch (undo_step) {
case 4:
de_key_size = le16_to_cpu(de2->key_size);
@@ -2747,6 +2790,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
u16 de_key_size = le16_to_cpu(de->key_size);
+ ni = ni->base;
if (sbi->options->windows_names &&
!valid_windows_name(sbi, (struct le_str *)&de_name->name_len))
return -EINVAL;
@@ -2794,6 +2838,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
int err;
struct NTFS_DE *de2 = NULL;
int undo = 0;
+ const int way = 1; /* Hope compiler removes below 'else'. */
/*
* There are two possible ways to rename:
@@ -2805,29 +2850,30 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
* Second way may result to bad inode if we can't add new name
* and then can't restore (add) old name.
*/
-
- /*
- * Way 1 - Add new + remove old.
- */
- err = ni_add_name(new_dir_ni, ni, new_de);
- if (!err) {
+ if (way == 1) {
+ /*
+ * Way 1 - Add new + remove old.
+ */
+ err = ni_add_name(new_dir_ni, ni, new_de);
+ if (!err) {
+ err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
+ if (err &&
+ ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
+ _ntfs_bad_inode(&ni->vfs_inode);
+ }
+ } else {
+ /*
+ * Way 2 - Remove old + add new.
+ */
err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
- if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
- _ntfs_bad_inode(&ni->vfs_inode);
+ if (!err) {
+ err = ni_add_name(new_dir_ni, ni, new_de);
+ if (err &&
+ !ni_remove_name_undo(dir_ni, ni, de, de2, undo))
+ _ntfs_bad_inode(&ni->vfs_inode);
+ }
}
- /*
- * Way 2 - Remove old + add new.
- */
- /*
- * err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
- * if (!err) {
- * err = ni_add_name(new_dir_ni, ni, new_de);
- * if (err && !ni_remove_name_undo(dir_ni, ni, de, de2, undo))
- * *is_bad = true;
- * }
- */
-
return err;
}
@@ -2950,6 +2996,9 @@ int ni_write_parents(struct ntfs_inode *ni, int sync)
struct ntfs_sb_info *sbi = ni->mi.sbi;
struct super_block *sb = sbi->sb;
+ if (!is_ni_base(ni))
+ return 0;
+
while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL,
NULL))) {
struct inode *dir;
@@ -3132,6 +3181,9 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint)
if (is_bad_inode(inode) || sb_rdonly(sb))
return 0;
+ if (!is_ni_base(ni))
+ return 0;
+
/* Avoid any operation if inode is bad. */
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
@@ -3316,10 +3368,84 @@ int ni_allocate_da_blocks_locked(struct ntfs_inode *ni)
/*
* Normal file allocates clusters in 'attr_set_size'
*/
- err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run,
+ err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, &ni->file.run,
ni->vfs_inode.i_size, &ni->i_valid,
false, NULL, true);
}
return err;
}
+
+/*
+ * Helper function to read ADS.
+ * bytes = iov_iter_count(iter) is not 0. Checked by caller;
+ */
+ssize_t ni_query_ads(struct ntfs_inode *ni, loff_t *pos, struct iov_iter *iter)
+{
+ ssize_t ret = 0;
+ struct ntfs_sb_info *sbi = ni->mi.sbi;
+ size_t bytes = iov_iter_count(iter);
+ loff_t end = *pos + bytes;
+ char *buf = NULL;
+ struct ATTR_LIST_ENTRY *le = NULL;
+ u64 vbo = 0;
+ struct ATTRIB *attr;
+ size_t done, used;
+ int err;
+
+ /*
+ * Enumerate ADS.
+ */
+ ni = ni->base;
+ attr = NULL;
+ while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) {
+ if (attr->type != ATTR_DATA || !attr->name_len)
+ continue;
+
+ if (!buf) {
+ buf = kmalloc(PAGE_SIZE, GFP_NOFS);
+ if (!buf)
+ return -ENOMEM;
+ }
+
+ /* attr - named DATA attribute (ADS). */
+ err = ntfs_utf16_to_nls(sbi, attr_name(attr), attr->name_len,
+ buf, PAGE_SIZE);
+ if (err < 0) {
+ ret = err;
+ break;
+ }
+
+ /*
+ * err is the length of ADS name in bytes.
+ * Copy pseudo data interval [vbo, err + 1).
+ * into 'iter': [*pos, bytes)
+ */
+ /* Add \n as streams name separator. */
+ buf[err++] = '\n';
+
+ if (vbo >= end) {
+ iov_iter_zero(bytes, iter);
+ break;
+ }
+
+ if (vbo + err > *pos) {
+ size_t off = *pos - vbo;
+ used = err - off;
+ done = copy_to_iter(Add2Ptr(buf, off), min(used, bytes),
+ iter);
+ } else {
+ done = 0;
+ }
+
+ ret += done;
+ *pos += done;
+ if (done >= bytes)
+ break;
+ bytes -= done;
+ vbo += err;
+ }
+ kfree(buf);
+ return ret;
+}
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index cc24c23e4db9..a7cddce30136 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -648,6 +648,14 @@ static inline void *enum_rstbl(struct RESTART_TABLE *t, void *c)
}
/*
+ * dp_range_ok - true if [j, j + count) fits in a page_lcns[cap] array.
+ */
+static inline bool dp_range_ok(size_t j, u32 count, u32 cap)
+{
+ return j < cap && count <= cap - j;
+}
+
+/*
* find_dp - Search for a @vcn in Dirty Page Table.
*/
static inline struct DIR_PAGE_ENTRY *find_dp(struct RESTART_TABLE *dptbl,
@@ -789,6 +797,20 @@ static bool check_rstbl(const struct RESTART_TABLE *rt, size_t bytes)
return true;
}
+static bool check_dp_table(const struct RESTART_TABLE *dptbl)
+{
+ u32 rsize = le16_to_cpu(dptbl->size);
+ struct DIR_PAGE_ENTRY *dp = NULL;
+
+ while ((dp = enum_rstbl((struct RESTART_TABLE *)dptbl, dp))) {
+ if (struct_size(dp, page_lcns, le32_to_cpu(dp->lcns_follow)) >
+ rsize)
+ return false;
+ }
+
+ return true;
+}
+
/*
* free_rsttbl_idx - Free a previously allocated index a Restart Table.
*/
@@ -853,6 +875,9 @@ static inline struct RESTART_TABLE *extend_rsttbl(struct RESTART_TABLE *tbl,
u32 used = le16_to_cpu(tbl->used);
struct RESTART_TABLE *rt;
+ if (used + add > U16_MAX)
+ return NULL;
+
rt = init_rsttbl(esize, used + add);
if (!rt)
return NULL;
@@ -4290,6 +4315,11 @@ check_dirty_page_table:
goto out;
}
+ if (!check_dp_table(rt)) {
+ err = -EINVAL;
+ goto out;
+ }
+
dptbl = kmemdup(rt, t32, GFP_NOFS);
if (!dptbl) {
err = -ENOMEM;
@@ -5082,6 +5112,13 @@ find_dirty_page:
/* Shorten length by any Lcns which were deleted. */
saved_len = dlen;
+ if (!dp_range_ok(le64_to_cpu(lrh->target_vcn) - le64_to_cpu(dp->vcn),
+ le16_to_cpu(lrh->lcns_follow),
+ le32_to_cpu(dp->lcns_follow))) {
+ err = -EINVAL;
+ goto out;
+ }
+
for (i = le16_to_cpu(lrh->lcns_follow); i; i--) {
size_t j;
u32 alen, voff;
diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c
index 7c4db816c43d..97c04ab2763a 100644
--- a/fs/ntfs3/fsntfs.c
+++ b/fs/ntfs3/fsntfs.c
@@ -88,6 +88,13 @@ const __le16 SQ_NAME[2] = {
const __le16 SR_NAME[2] = {
cpu_to_le16('$'), cpu_to_le16('R'),
};
+const __le16 QUERY_STREAMS[13] = {
+ cpu_to_le16('q'), cpu_to_le16('u'), cpu_to_le16('e'), cpu_to_le16('r'),
+ cpu_to_le16('y'), cpu_to_le16('_'), cpu_to_le16('s'), cpu_to_le16('t'),
+ cpu_to_le16('r'), cpu_to_le16('e'), cpu_to_le16('a'), cpu_to_le16('m'),
+ cpu_to_le16('s'),
+};
+
#ifdef CONFIG_NTFS3_LZX_XPRESS
const __le16 WOF_NAME[17] = {
@@ -122,7 +129,6 @@ static const __le16 COM_NAME[3] = {
static const __le16 LPT_NAME[3] = {
cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'),
};
-
// clang-format on
/*
@@ -236,7 +242,7 @@ int ntfs_extend_init(struct ntfs_sb_info *sbi)
}
/* Try to find $ObjId */
- inode2 = dir_search_u(inode, &NAME_OBJID, NULL);
+ inode2 = dir_search(inode, &NAME_OBJID);
if (inode2 && !IS_ERR(inode2)) {
if (is_bad_inode(inode2)) {
iput(inode2);
@@ -247,21 +253,21 @@ int ntfs_extend_init(struct ntfs_sb_info *sbi)
}
/* Try to find $Quota */
- inode2 = dir_search_u(inode, &NAME_QUOTA, NULL);
+ inode2 = dir_search(inode, &NAME_QUOTA);
if (inode2 && !IS_ERR(inode2)) {
sbi->quota_no = inode2->i_ino;
iput(inode2);
}
/* Try to find $Reparse */
- inode2 = dir_search_u(inode, &NAME_REPARSE, NULL);
+ inode2 = dir_search(inode, &NAME_REPARSE);
if (inode2 && !IS_ERR(inode2)) {
sbi->reparse.ni = ntfs_i(inode2);
sbi->reparse_no = inode2->i_ino;
}
/* Try to find $UsnJrnl */
- inode2 = dir_search_u(inode, &NAME_USNJRNL, NULL);
+ inode2 = dir_search(inode, &NAME_USNJRNL);
if (inode2 && !IS_ERR(inode2)) {
sbi->usn_jrnl_no = inode2->i_ino;
iput(inode2);
@@ -475,7 +481,7 @@ bool ntfs_check_free_space(struct ntfs_sb_info *sbi, CLST clen, CLST mlen,
avail = free - (zlen + clen);
- /*
+ /*
* When delalloc is active then keep in mind some reserved space.
* The worst case: 1 mft record per each ~500 clusters.
*/
@@ -1705,6 +1711,8 @@ struct ntfs_inode *ntfs_new_inode(struct ntfs_sb_info *sbi, CLST rno,
goto out;
}
+ ni->base = ni;
+
out:
if (err) {
make_bad_inode(inode);
@@ -2664,6 +2672,12 @@ int ntfs_set_label(struct ntfs_sb_info *sbi, u8 *label, int len)
if (err < 0)
goto out;
+ if (uni->ads_len) {
+ /* Undo delimiter parse */
+ uni->len += uni->ads_len + 1;
+ uni->ads_len = 0;
+ }
+
uni_bytes = uni->len * sizeof(u16);
if (uni_bytes > NTFS_LABEL_MAX_LENGTH * sizeof(u16)) {
ntfs_warn(sbi->sb, "new label is too long");
diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index 4afacfc6ff89..689712d3463d 100644
--- a/fs/ntfs3/index.c
+++ b/fs/ntfs3/index.c
@@ -1325,6 +1325,7 @@ pop_level:
/* Pop one level. */
if (n) {
fnd_pop(fnd);
+ kfree(n->index);
kfree(n);
}
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index 4ecd7ebaa655..286678e23824 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -18,12 +18,16 @@
#include "ntfs.h"
#include "ntfs_fs.h"
+struct IGET5_PARAM {
+ const struct MFT_REF *ref;
+ const struct cpu_str *name;
+};
+
/*
* ntfs_read_mft - Read record and parse MFT.
*/
-static struct inode *ntfs_read_mft(struct inode *inode,
- const struct cpu_str *name,
- const struct MFT_REF *ref)
+static int ntfs_read_mft(struct inode *inode, const struct cpu_str *name,
+ const struct MFT_REF *ref)
{
int err = 0;
struct ntfs_inode *ni = ntfs_i(inode);
@@ -46,6 +50,7 @@ static struct inode *ntfs_read_mft(struct inode *inode,
struct MFT_REC *rec;
struct runs_tree *run;
struct timespec64 ts;
+ const __le16 *aname;
inode->i_op = NULL;
/* Setup 'uid' and 'gid' */
@@ -144,6 +149,7 @@ next_attr:
roff = attr->non_res ? 0 : le16_to_cpu(attr->res.data_off);
rsize = attr->non_res ? 0 : le32_to_cpu(attr->res.data_size);
asize = le32_to_cpu(attr->size);
+ aname = attr_name(attr);
/*
* Really this check was done in 'ni_enum_attr_ex' -> ... 'mi_enum_attr'.
@@ -230,10 +236,10 @@ next_attr:
if (attr->name_len &&
((ino != MFT_REC_BADCLUST || !attr->non_res ||
attr->name_len != ARRAY_SIZE(BAD_NAME) ||
- memcmp(attr_name(attr), BAD_NAME, sizeof(BAD_NAME))) &&
+ memcmp(aname, BAD_NAME, sizeof(BAD_NAME))) &&
(ino != MFT_REC_SECURE || !attr->non_res ||
attr->name_len != ARRAY_SIZE(SDS_NAME) ||
- memcmp(attr_name(attr), SDS_NAME, sizeof(SDS_NAME))))) {
+ memcmp(aname, SDS_NAME, sizeof(SDS_NAME))))) {
/* File contains stream attribute. Ignore it. */
goto next_attr;
}
@@ -253,14 +259,11 @@ next_attr:
else
ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED;
- if (!attr->non_res) {
- ni->i_valid = inode->i_size = rsize;
- inode_set_bytes(inode, rsize);
- }
-
mode = S_IFREG | (0777 & sbi->options->fs_fmask_inv);
if (!attr->non_res) {
+ ni->i_valid = inode->i_size = rsize;
+ inode_set_bytes(inode, rsize);
ni->ni_flags |= NI_FLAG_RESIDENT;
goto next_attr;
}
@@ -501,16 +504,136 @@ end_enum:
if (ino == MFT_REC_MFT && !sb->s_root)
sbi->mft.ni = NULL;
- unlock_new_inode(inode);
-
- return inode;
+ return 0;
out:
if (ino == MFT_REC_MFT && !sb->s_root)
sbi->mft.ni = NULL;
- iget_failed(inode);
- return ERR_PTR(err);
+ return err;
+}
+
+/*
+ * ntfs_init_ads_node
+ *
+ * This function scans base inode for given ADS.
+ * And init inode associated with this ADS
+ */
+static int ntfs_init_ads_node(struct inode *inode, const __le16 *ads_name,
+ u8 ads_len, u32 flags)
+{
+ int err = -EINVAL;
+ struct ntfs_inode *ni = ntfs_i(inode);
+ struct ntfs_inode *nb = ni->base;
+ struct ntfs_sb_info *sbi = nb->mi.sbi;
+ struct ATTR_LIST_ENTRY *le = NULL;
+ struct ATTRIB *attr = NULL;
+ u16 roff, asize;
+ u64 svcn;
+
+ if (nb->ni_flags & NI_FLAG_DIR)
+ return -EINVAL; /* no ADS for directories. */
+
+ ni->mi.sbi = sbi;
+ ni->mi.rno = inode->i_ino;
+
+ if (ads_len == ARRAY_SIZE(QUERY_STREAMS) &&
+ !memcmp(ads_name, QUERY_STREAMS, sizeof(QUERY_STREAMS))) {
+ goto ok; /* use goto to reduce tab pressure. */
+ }
+
+ /* Enumerate all attributes in record. */
+ while ((attr = ni_enum_attr_ex(nb, attr, &le, NULL))) {
+ if (attr->type == ATTR_DATA && attr->name_len &&
+ ads_len == attr->name_len &&
+ !memcmp(ads_name, attr_name(attr), ads_len * sizeof(u16))) {
+ /* We have found the ADS to open. */
+ break;
+ }
+ }
+
+ if (!attr) {
+ if (!(flags & LOOKUP_CREATE)) {
+ /* Do not create ADS. */
+ return -ENOENT;
+ }
+
+ /* Create new ADS. */
+ err = ni_insert_resident(nb, 0, ATTR_DATA, ads_name, ads_len,
+ &attr, NULL, NULL);
+ if (err) {
+ /* Looks like the only reasons: ENOSPC/ENOMEM .*/
+ return err;
+ }
+ }
+
+ if (is_attr_sparsed(attr))
+ ni->std_fa |= FILE_ATTRIBUTE_SPARSE_FILE;
+ else
+ ni->std_fa &= ~FILE_ATTRIBUTE_SPARSE_FILE;
+
+ if (is_attr_compressed(attr))
+ ni->std_fa |= FILE_ATTRIBUTE_COMPRESSED;
+ else
+ ni->std_fa &= ~FILE_ATTRIBUTE_COMPRESSED;
+
+ if (is_attr_encrypted(attr))
+ ni->std_fa |= FILE_ATTRIBUTE_ENCRYPTED;
+ else
+ ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED;
+
+ if (!attr->non_res) {
+ ni->ni_flags |= NI_FLAG_RESIDENT;
+ ni->i_valid = inode->i_size = le32_to_cpu(attr->res.data_size);
+ inode_set_bytes(inode, inode->i_size);
+ goto ok;
+ }
+
+ inode_set_bytes(inode, attr_ondisk_size(attr));
+ ni->i_valid = le64_to_cpu(attr->nres.valid_size);
+ inode->i_size = le64_to_cpu(attr->nres.data_size);
+
+ if (!attr->nres.alloc_size)
+ goto ok;
+
+ roff = le16_to_cpu(attr->nres.run_off);
+ asize = le32_to_cpu(attr->size);
+
+ if (roff > asize) {
+ /* This case should be checked in mi_enum_attr */
+ return -EINVAL;
+ }
+
+ svcn = le64_to_cpu(attr->nres.svcn);
+ err = run_unpack_ex(&ni->file.run, sbi, ni->mi.rno, svcn,
+ le64_to_cpu(attr->nres.evcn), svcn,
+ Add2Ptr(attr, roff), asize - roff);
+ if (err < 0) {
+ /* run_unpack_ex marks volume dirty, if logical error. */
+ return err;
+ }
+
+ok:
+ /* Keep ADS name (little endian). */
+ ni->file.ads.name = kmemdup(ads_name, ads_len * sizeof(u16), GFP_NOFS);
+ if (!ni->file.ads.name)
+ return -ENOMEM;
+ ni->file.ads.len = ads_len;
+
+ set_nlink(inode, 1);
+
+ init_rwsem(&ni->file.run_lock);
+ /* Most fields are the same as the base's? */
+ inode->i_op = nb->vfs_inode.i_op;
+ inode->i_fop = nb->vfs_inode.i_fop;
+ inode->i_mapping->a_ops = nb->vfs_inode.i_mapping->a_ops;
+ inode->i_flags = nb->vfs_inode.i_flags;
+ inode->i_mode = nb->vfs_inode.i_mode;
+ inode->i_uid = nb->vfs_inode.i_uid;
+ inode->i_gid = nb->vfs_inode.i_gid;
+ inode->i_generation = nb->vfs_inode.i_generation;
+
+ return 0;
}
/*
@@ -520,44 +643,120 @@ out:
*/
static int ntfs_test_inode(struct inode *inode, void *data)
{
- struct MFT_REF *ref = data;
+ const struct IGET5_PARAM *ig5 = data;
+ struct ntfs_inode *ni;
+ const struct cpu_str *name;
+
+ if (ino_get(ig5->ref) != inode->i_ino)
+ return 0;
+
+ ni = ntfs_i(inode);
+
+ if (ni->ni_flags & NI_FLAG_DIR) {
+ /* No ads for directories. */
+ return 1;
+ }
+
+ name = ig5->name;
+ if (!name || !name->ads_len) {
+ if (!ni->file.ads.len) {
+ /* default file (not ads) match. */
+ return 1;
+ }
+ } else if (ni->file.ads.len == name->ads_len &&
+ !memcmp(ni->file.ads.name, &name->name[name->len + 1],
+ name->ads_len * sizeof(u16))) {
+ /* ads name match. */
+ return 1;
+ }
- return ino_get(ref) == inode->i_ino;
+ return 0;
}
static int ntfs_set_inode(struct inode *inode, void *data)
{
- const struct MFT_REF *ref = data;
+ const struct IGET5_PARAM *ig5 = data;
- inode->i_ino = ino_get(ref);
+ inode->i_ino = ino_get(ig5->ref);
return 0;
}
-struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
- const struct cpu_str *name)
+struct inode *ntfs_iget5_flags(struct super_block *sb,
+ const struct MFT_REF *ref,
+ const struct cpu_str *name, u32 flags)
{
- struct inode *inode;
+ int err;
+ /* Pack params to pass in iget5_locked. */
+ struct IGET5_PARAM ig5 = { ref, name };
+ u64 ino = ino_get(ref);
+ struct inode *inode, *base = NULL;
+ bool ads = name && name->ads_len;
+ struct ntfs_inode *ni;
+
+ if (ads) {
+ /* First get base inode */
+ base = ntfs_iget5_flags(sb, ref, NULL, 0);
+ if (IS_ERR(base))
+ return base;
+ }
+
+ inode = iget5_locked(sb, ino, ntfs_test_inode, ntfs_set_inode, &ig5);
+ if (unlikely(!inode)) {
+ err = -ENOMEM;
+ goto out;
+ }
- inode = iget5_locked(sb, ino_get(ref), ntfs_test_inode, ntfs_set_inode,
- (void *)ref);
- if (unlikely(!inode))
- return ERR_PTR(-ENOMEM);
+ ni = ntfs_i(inode);
/* If this is a freshly allocated inode, need to read it now. */
- if (inode_state_read_once(inode) & I_NEW)
- inode = ntfs_read_mft(inode, name, ref);
- else if (ref->seq != ntfs_i(inode)->mi.mrec->seq) {
+ if (inode_state_read_once(inode) & I_NEW) {
+ if (!base) {
+ /* default inode. generic file/dir. */
+ ni->base = ni;
+ } else {
+ /* inode + ads */
+ ni->base = ntfs_i(base);
+ base = NULL; /* keep reference incremented (instead of ihold). */
+ }
+
+ if (ads) {
+ /* base record is loaded. Init ads node. */
+ err = ntfs_init_ads_node(
+ inode, (__le16 *)&name->name[name->len + 1],
+ name->ads_len, flags);
+ } else {
+ err = ntfs_read_mft(inode, name, ref);
+ }
+
+ if (!err) {
+ unlock_new_inode(inode);
+ } else {
+ iget_failed(inode);
+ /* Do not mark volume dirty if ADS not found. */
+ if (ads)
+ goto out;
+ }
+ } else if (!ads && ref->seq != ni->mi.mrec->seq) {
/*
* Sequence number is not expected.
* Looks like inode was reused but caller uses the old reference
*/
iput(inode);
- inode = ERR_PTR(-ESTALE);
+ err = -ESTALE;
+ } else {
+ err = 0;
}
- if (IS_ERR(inode))
+ if (err)
ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
+out:
+ if (base)
+ iput(base);
+
+ if (err)
+ return ERR_PTR(err);
+
return inode;
}
@@ -706,8 +905,8 @@ int ntfs_set_size(struct inode *inode, u64 new_size)
ni->i_valid = new_size;
/* last 'true' means keep preallocated. */
- err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
- &ni->i_valid, true);
+ err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name, ni->file.ads.len,
+ &ni->file.run, new_size, &ni->i_valid, true);
up_write(&ni->file.run_lock);
ni_unlock(ni);
@@ -801,7 +1000,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
if (lcn == RESIDENT_LCN) {
if (offset >= clen) {
- __free_page(virt_to_page(res));
+ if (res)
+ __free_page(virt_to_page(res));
if (flags & IOMAP_REPORT) {
/* special code for report. */
return -ENOENT;
@@ -892,7 +1092,8 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
struct ATTRIB *attr;
struct mft_inode *mi;
- attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0,
+ attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA,
+ ni->file.ads.name, ni->file.ads.len,
NULL, &mi);
if (!attr || attr->non_res) {
err = -EINVAL;
@@ -1214,6 +1415,15 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
if (!fnd)
ni_lock_dir(dir_ni);
+ if (sbi->options->ads) {
+ const char *ads = strchr(name->name + 1, ':');
+ if (ads && ads[1]) {
+ ntfs_warn(sb, "failed to create ads");
+ err = -EINVAL;
+ goto out1;
+ }
+ }
+
dir_root = indx_get_root(&dir_ni->dir, dir_ni, NULL, NULL);
if (!dir_root) {
err = -EINVAL;
diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c
index 7bf889a4ca35..ec59bbabd3c5 100644
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@ -78,21 +78,23 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
int err;
if (!uni)
- inode = ERR_PTR(-ENOMEM);
- else {
- err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name,
- dentry->d_name.len, uni, NTFS_NAME_LEN,
- UTF16_HOST_ENDIAN);
- if (err < 0)
- inode = ERR_PTR(err);
- else {
- ni_lock_dir(ni);
- inode = dir_search_u(dir, uni, NULL);
- ni_unlock(ni);
- }
+ return ERR_PTR(-ENOMEM);
+
+ err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name,
+ dentry->d_name.len, uni, NTFS_NAME_LEN,
+ UTF16_HOST_ENDIAN);
+
+ if (err < 0) {
kfree(uni);
+ return ERR_PTR(err);
}
+ ni_lock_dir(ni);
+ inode = dir_search_flags(dir, uni, NULL, flags);
+ ni_unlock(ni);
+
+ kfree(uni);
+
/*
* Check for a null pointer
* If the MFT record of ntfs inode is not a base record, inode->i_op can be NULL.
@@ -100,7 +102,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
*/
if (!IS_ERR_OR_NULL(inode) && !inode->i_op) {
iput(inode);
- inode = ERR_PTR(-EINVAL);
+ return ERR_PTR(-EINVAL);
}
return d_splice_alias(inode, dentry);
@@ -173,7 +175,9 @@ static int ntfs_link(struct dentry *ode, struct inode *dir, struct dentry *de)
*/
static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
{
- struct ntfs_inode *ni = ntfs_i(dir);
+ struct ntfs_inode *dir_ni = ntfs_i(dir);
+ struct inode *inode = d_inode(dentry);
+ struct ntfs_inode *ni = ntfs_i(inode);
int err;
/* Avoid any operation if inode is bad. */
@@ -183,11 +187,21 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
if (unlikely(ntfs3_forced_shutdown(dir->i_sb)))
return -EIO;
- ni_lock_dir(ni);
-
- err = ntfs_unlink_inode(dir, dentry);
+ if (likely(is_ni_base(ni))) {
+ ni_lock_dir(dir_ni);
+ /* Remove general file/dir. */
+ err = ntfs_unlink_inode(dir, dentry);
+ ni_unlock(dir_ni);
+ } else {
+ ni_lock(ni);
+ /* Remove ADS. */
+ err = ni_remove_attr(ni, ATTR_DATA, ni->file.ads.name,
+ ni->file.ads.len, false, NULL);
+ ni_unlock(ni);
- ni_unlock(ni);
+ if (!err)
+ drop_nlink(inode);
+ }
return err;
}
@@ -273,6 +287,11 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir,
1024);
static_assert(PATH_MAX >= 4 * 1024);
+ if (!is_ni_base(ni)) {
+ /* No rename for ADS. */
+ return -EOPNOTSUPP;
+ }
+
/* Avoid any operation if inode is bad. */
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
diff --git a/fs/ntfs3/ntfs.h b/fs/ntfs3/ntfs.h
index 892f13e65d42..4589b16329c9 100644
--- a/fs/ntfs3/ntfs.h
+++ b/fs/ntfs3/ntfs.h
@@ -58,7 +58,7 @@ struct GUID {
*/
struct cpu_str {
u8 len;
- u8 unused;
+ u8 ads_len;
u16 name[];
};
@@ -170,6 +170,7 @@ extern const __le16 SDH_NAME[4];
extern const __le16 SO_NAME[2];
extern const __le16 SQ_NAME[2];
extern const __le16 SR_NAME[2];
+extern const __le16 QUERY_STREAMS[13];
extern const __le16 BAD_NAME[4];
extern const __le16 SDS_NAME[4];
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 6bd1a439e92f..5811d89d67b3 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -110,6 +110,7 @@ struct ntfs_mount_options {
unsigned prealloc : 1; /* Preallocate space when file is growing. */
unsigned nocase : 1; /* case insensitive. */
unsigned delalloc : 1; /* delay allocation. */
+ unsigned ads : 1; /* ads support. */
};
/* Special value to unpack and deallocate. */
@@ -410,6 +411,11 @@ struct ntfs_inode {
#ifdef CONFIG_NTFS3_LZX_XPRESS
struct folio *offs_folio;
#endif
+ /* Alternative data stream */
+ struct {
+ __le16 *name;
+ u8 len;
+ } ads;
} file;
};
@@ -421,6 +427,7 @@ struct ntfs_inode {
} attr_list;
size_t ni_flags; // NI_FLAG_XXX
+ struct ntfs_inode *base; /* ADS: points to base inode. Other: this. */
struct inode vfs_inode;
};
@@ -444,6 +451,11 @@ enum REPARSE_SIGN {
REPARSE_LINK = 3
};
+static inline bool is_ni_base(const struct ntfs_inode *ni)
+{
+ return ni == ni->base;
+}
+
/* Functions from attrib.c */
int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run,
struct runs_tree *run_da, CLST vcn, CLST lcn,
@@ -526,8 +538,14 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len,
int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
struct cpu_str *uni, u32 max_ulen,
enum utf16_endian endian);
-struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
- struct ntfs_fnd *fnd);
+struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni,
+ struct ntfs_fnd *fnd, u32 flags);
+static inline struct inode *dir_search(struct inode *dir,
+ const struct cpu_str *uni)
+{
+ return dir_search_flags(dir, uni, NULL, 0);
+}
+
bool dir_is_empty(struct inode *dir);
extern const struct file_operations ntfs_dir_operations;
@@ -622,6 +640,7 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data);
int ni_write_parents(struct ntfs_inode *ni, int sync);
int ni_allocate_da_blocks(struct ntfs_inode *ni);
int ni_allocate_da_blocks_locked(struct ntfs_inode *ni);
+ssize_t ni_query_ads(struct ntfs_inode *ni, loff_t *pos, struct iov_iter *iter);
/* Globals from fslog.c */
bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes);
@@ -679,7 +698,6 @@ static inline int ntfs_read_bh(struct ntfs_sb_info *sbi,
{
return ntfs_read_bh_ra(sbi, run, vbo, rhdr, bytes, nb, NULL);
}
-
int ntfs_get_bh(struct ntfs_sb_info *sbi, const struct runs_tree *run, u64 vbo,
u32 bytes, struct ntfs_buffers *nb);
int ntfs_write_bh(struct ntfs_sb_info *sbi, struct NTFS_RECORD_HEADER *rhdr,
@@ -772,8 +790,15 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi,
const struct NTFS_DUP_INFO *dup, int sync);
/* Globals from inode.c */
-struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
- const struct cpu_str *name);
+struct inode *ntfs_iget5_flags(struct super_block *sb,
+ const struct MFT_REF *ref,
+ const struct cpu_str *name, u32 flags);
+static inline struct inode *ntfs_iget5(struct super_block *sb,
+ const struct MFT_REF *ref,
+ const struct cpu_str *name)
+{
+ return ntfs_iget5_flags(sb, ref, name, 0);
+}
int ntfs_set_size(struct inode *inode, u64 new_size);
int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc);
int ntfs_sync_inode(struct inode *inode);
@@ -1219,27 +1244,27 @@ static inline void mi_clear(struct mft_inode *mi)
static inline void ni_lock(struct ntfs_inode *ni)
{
- mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_NORMAL);
+ mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_NORMAL);
}
static inline void ni_lock_dir(struct ntfs_inode *ni)
{
- mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_PARENT);
+ mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_PARENT);
}
static inline void ni_lock_dir2(struct ntfs_inode *ni)
{
- mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_PARENT2);
+ mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_PARENT2);
}
static inline void ni_unlock(struct ntfs_inode *ni)
{
- mutex_unlock(&ni->ni_lock);
+ mutex_unlock(&ni->base->ni_lock);
}
static inline int ni_trylock(struct ntfs_inode *ni)
{
- return mutex_trylock(&ni->ni_lock);
+ return mutex_trylock(&ni->base->ni_lock);
}
static inline int attr_load_runs_attr(struct ntfs_inode *ni,
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index 94cb57007893..ea9dde56f968 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -23,6 +23,7 @@
* allocated_size - Total size of clusters allocated for non-resident content
* total_size - Actual size of allocated clusters for sparse or compressed attributes
* - Constraint: valid_size <= data_size <= allocated_size
+ * ADS - Alternative data stream: Named data attribute (0x80)
*
* WSL - Windows Subsystem for Linux
* https://docs.microsoft.com/en-us/windows/wsl/file-permissions
@@ -271,6 +272,8 @@ enum Opt {
Opt_nocase,
Opt_delalloc,
Opt_delalloc_bool,
+ Opt_ads,
+ Opt_ads_bool,
Opt_err,
};
@@ -297,6 +300,8 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = {
fsparam_flag("nocase", Opt_nocase),
fsparam_flag("delalloc", Opt_delalloc),
fsparam_bool("delalloc", Opt_delalloc_bool),
+ fsparam_flag("ads", Opt_ads),
+ fsparam_bool("ads", Opt_ads_bool),
{}
};
// clang-format on
@@ -420,6 +425,12 @@ static int ntfs_fs_parse_param(struct fs_context *fc,
case Opt_delalloc_bool:
opts->delalloc = result.boolean;
break;
+ case Opt_ads:
+ opts->ads = 1;
+ break;
+ case Opt_ads_bool:
+ opts->ads = result.boolean;
+ break;
default:
/* Should not be here unless we forget add case. */
return -EINVAL;
@@ -791,6 +802,8 @@ static int ntfs_show_options(struct seq_file *m, struct dentry *root)
seq_puts(m, ",nocase");
if (opts->delalloc)
seq_puts(m, ",delalloc");
+ if (opts->ads)
+ seq_puts(m, ",ads");
return 0;
}
@@ -1871,6 +1884,7 @@ static int ntfs_init_fs_context(struct fs_context *fc)
opts->fs_gid = current_gid();
opts->fs_fmask_inv = opts->fs_dmask_inv = ~current_umask();
opts->prealloc = 1;
+ opts->ads = 1;
#ifdef CONFIG_NTFS3_FS_POSIX_ACL
/* Set the default value 'acl' */
diff --git a/fs/open.c b/fs/open.c
index 408925d7bd0b..6b1c14e684a9 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -570,9 +570,12 @@ retry:
SYSCALL_DEFINE1(fchdir, unsigned int, fd)
{
- CLASS(fd_raw, f)(fd);
int error;
+ if ((int)fd == FD_FAILFS_ROOT)
+ return failfs_current_chdir();
+
+ CLASS(fd_raw, f)(fd);
if (fd_empty(f))
return -EBADF;
@@ -615,6 +618,52 @@ dput_and_out:
return error;
}
+SYSCALL_DEFINE2(fchroot, int, fd, unsigned int, flags)
+{
+ struct path path;
+ int error;
+
+ if (flags)
+ return -EINVAL;
+
+ if (fd == FD_FAILFS_ROOT) {
+ if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT)) {
+ if (!task_no_new_privs(current))
+ return -EPERM;
+ /* A shared fs_struct lets a sibling exec setuid past the check above. */
+ if (current->fs->users != 1)
+ return -EINVAL;
+ /* Moving the root to failfs lifts the old root's ".." barrier. */
+ if (current_chrooted())
+ return -EPERM;
+ }
+ failfs_get_root(&path);
+ } else {
+ CLASS(fd_raw, f)(fd);
+ if (fd_empty(f))
+ return -EBADF;
+
+ if (!d_can_lookup(fd_file(f)->f_path.dentry))
+ return -ENOTDIR;
+
+ error = file_permission(fd_file(f), MAY_EXEC | MAY_CHDIR);
+ if (error)
+ return error;
+
+ if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
+ return -EPERM;
+
+ path = fd_file(f)->f_path;
+ path_get(&path);
+ }
+
+ error = security_path_chroot(&path);
+ if (!error)
+ set_fs_root(current->fs, &path);
+ path_put(&path);
+ return error;
+}
+
int chmod_common(const struct path *path, umode_t mode)
{
struct inode *inode = path->dentry->d_inode;
diff --git a/fs/pipe.c b/fs/pipe.c
index 8f98ca864cb4..95c2e04c5267 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -111,75 +111,95 @@ void pipe_double_lock(struct pipe_inode_info *pipe1,
pipe_lock(pipe2);
}
-#define PIPE_PREALLOC_MAX 8
+static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc)
+{
+ if (!prealloc->count)
+ return NULL;
-struct anon_pipe_prealloc {
- struct page *pages[PIPE_PREALLOC_MAX];
- unsigned int count;
-};
+ prealloc->count--;
+
+ return prealloc->pages[prealloc->count];
+}
+
+/* Push a page to the prealloc pool. Returns true if added, false if full. */
+static bool anon_pipe_prealloc_push(struct anon_pipe_prealloc *prealloc,
+ struct page *page)
+{
+ if (prealloc->count >= PIPE_PREALLOC_MAX)
+ return false;
+ prealloc->pages[prealloc->count++] = page;
+ return true;
+}
/*
- * Pre-allocate pages outside pipe->mutex for multi-page writes.
- * alloc_page() with GFP_HIGHUSER can sleep in reclaim and runs memcg
- * charging; doing it under the mutex stalls a concurrent reader.
- *
- * Loop alloc_page() instead of alloc_pages_bulk_*(): the bulk path refuses
- * __GFP_ACCOUNT under memcg (see commit 8dcb3060d81d "memcg: page_alloc:
- * skip bulk allocator for __GFP_ACCOUNT") and silently degrades to a single
- * page. A per-page loop keeps memcg accounting and the task NUMA mempolicy
- * honoured for every page; the per-call overhead is small compared to the
- * pipe->mutex hold-time being shrunk. Any shortfall is covered by the
- * in-lock alloc_page() fallback in anon_pipe_get_page().
+ * Top up the pipe's own pool, then take pipe->mutex and return with it held.
+ * The shortfall is allocated outside the lock; the push and the caller's write
+ * then run under a single lock acquisition, avoiding a separate prefill
+ * lock/unlock cycle. anon_pipe_get_page() drains the pool instead of allocating
+ * under the lock.
*/
-static void anon_pipe_get_page_prealloc(struct anon_pipe_prealloc *prealloc,
- size_t total_len)
+static void anon_pipe_prefill_and_lock(struct pipe_inode_info *pipe, size_t total_len)
{
- unsigned int want, i;
- struct page *page;
-
- prealloc->count = 0;
- if (total_len <= PAGE_SIZE)
- return;
+ struct page *pages[PIPE_PREALLOC_MAX];
+ unsigned int want, have, need, n = 0;
want = min_t(unsigned int, DIV_ROUND_UP(total_len, PAGE_SIZE),
PIPE_PREALLOC_MAX);
+ /* Unlocked read; the pool is refilled under the lock below. */
+ have = min_t(unsigned int, READ_ONCE(pipe->prealloc.count), want);
+ need = want - have;
+
+ if (!need) {
+ mutex_lock(&pipe->mutex);
+ return;
+ }
+
+ while (n < need) {
+ struct page *page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
- for (i = 0; i < want; i++) {
- page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
if (!page)
break;
- prealloc->pages[prealloc->count++] = page;
+ pages[n++] = page;
}
+
+ mutex_lock(&pipe->mutex);
+ while (n && anon_pipe_prealloc_push(&pipe->prealloc, pages[n - 1]))
+ n--;
+
+ /*
+ * Just flush any extra page that got affected by the TOCTOU
+ * effect
+ */
+ while (n)
+ put_page(pages[--n]);
}
-static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc)
+/*
+ * Called with pipe->mutex held. Trim the pool down to PIPE_PREALLOC_KEEP under
+ * the lock, drop it, then free the excess outside the critical section.
+ */
+static void anon_pipe_trim_and_unlock(struct pipe_inode_info *pipe)
{
- if (!prealloc->count)
- return NULL;
+ struct page *excess[PIPE_PREALLOC_MAX];
+ unsigned int nexcess = 0;
- prealloc->count--;
+ while (pipe->prealloc.count > PIPE_PREALLOC_KEEP)
+ excess[nexcess++] = anon_pipe_prealloc_pop(&pipe->prealloc);
+ mutex_unlock(&pipe->mutex);
- return prealloc->pages[prealloc->count];
+ while (nexcess)
+ put_page(excess[--nexcess]);
}
-static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe,
- struct anon_pipe_prealloc *prealloc)
+static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe)
{
struct page *page;
- /* Drain prealloc first to keep tmp_page[] hot for later small writes. */
- page = anon_pipe_prealloc_pop(prealloc);
+ /* Drain the prealloc pool before allocating. Called with mutex held. */
+ page = anon_pipe_prealloc_pop(&pipe->prealloc);
if (page)
return page;
- for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
- if (pipe->tmp_page[i]) {
- page = pipe->tmp_page[i];
- pipe->tmp_page[i] = NULL;
- return page;
- }
- }
-
/* FWIW: This is called with pipe->mutex held */
return alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
}
@@ -187,48 +207,11 @@ static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe,
static void anon_pipe_put_page(struct pipe_inode_info *pipe,
struct page *page)
{
- if (page_count(page) == 1) {
- for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
- if (!pipe->tmp_page[i]) {
- pipe->tmp_page[i] = page;
- return;
- }
- }
- }
-
- put_page(page);
-}
-
-/*
- * Stash leftover prealloc pages in tmp_page[] so the next write to this
- * pipe gets a hot page without entering the allocator.
- */
-static void anon_pipe_refill_tmp_pages(struct pipe_inode_info *pipe,
- struct anon_pipe_prealloc *prealloc)
-{
- int i, idx;
-
- if (!prealloc->count)
+ if (page_count(page) == 1 &&
+ anon_pipe_prealloc_push(&pipe->prealloc, page))
return;
- for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
- if (pipe->tmp_page[i])
- continue;
- if (!prealloc->count)
- return;
- idx = --prealloc->count;
- pipe->tmp_page[i] = prealloc->pages[idx];
- prealloc->pages[idx] = NULL;
- }
-}
-
-/* Runs after mutex_unlock() to keep put_page() out of the critical section. */
-static void anon_pipe_free_pages(struct anon_pipe_prealloc *prealloc)
-{
- while (prealloc->count) {
- prealloc->count--;
- put_page(prealloc->pages[prealloc->count]);
- }
+ put_page(page);
}
static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
@@ -485,7 +468,8 @@ anon_pipe_read(struct kiocb *iocb, struct iov_iter *to)
}
if (pipe_is_empty(pipe))
wake_next_reader = false;
- mutex_unlock(&pipe->mutex);
+ /* Consumed buffers may have refilled the pool; trim it and unlock. */
+ anon_pipe_trim_and_unlock(pipe);
if (wake_writer)
wake_up_interruptible_sync_poll(&pipe->wr_wait, EPOLLOUT | EPOLLWRNORM);
@@ -524,7 +508,6 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
struct file *filp = iocb->ki_filp;
struct pipe_inode_info *pipe = filp->private_data;
- struct anon_pipe_prealloc prealloc;
unsigned int head;
ssize_t ret = 0;
size_t total_len = iov_iter_count(from);
@@ -548,9 +531,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
if (unlikely(total_len == 0))
return 0;
- anon_pipe_get_page_prealloc(&prealloc, total_len);
-
- mutex_lock(&pipe->mutex);
+ anon_pipe_prefill_and_lock(pipe, total_len);
if (!pipe->readers) {
if ((iocb->ki_flags & IOCB_NOSIGNAL) == 0)
@@ -607,7 +588,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
struct page *page;
int copied;
- page = anon_pipe_get_page(pipe, &prealloc);
+ page = anon_pipe_get_page(pipe);
if (unlikely(!page)) {
if (!ret)
ret = -ENOMEM;
@@ -671,11 +652,9 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
wake_next_writer = true;
}
out:
- anon_pipe_refill_tmp_pages(pipe, &prealloc);
if (pipe_is_full(pipe))
wake_next_writer = false;
- mutex_unlock(&pipe->mutex);
- anon_pipe_free_pages(&prealloc);
+ anon_pipe_trim_and_unlock(pipe);
/*
* If we do do a wakeup event, we do a 'sync' wakeup, because we
@@ -956,10 +935,8 @@ void free_pipe_info(struct pipe_inode_info *pipe)
if (pipe->watch_queue)
put_watch_queue(pipe->watch_queue);
#endif
- for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
- if (pipe->tmp_page[i])
- __free_page(pipe->tmp_page[i]);
- }
+ for (i = 0; i < pipe->prealloc.count; i++)
+ __free_page(pipe->prealloc.pages[i]);
kfree(pipe->bufs);
kfree(pipe);
}
diff --git a/fs/proc/Kconfig b/fs/proc/Kconfig
index 6ae966c561e7..24f5a36f45df 100644
--- a/fs/proc/Kconfig
+++ b/fs/proc/Kconfig
@@ -80,10 +80,9 @@ config PROC_VMCORE_DEVICE_RAM
Relevant architectures should select NEED_PROC_VMCORE_DEVICE_RAM.
-config PROC_SYSCTL
+config SYSCTL
bool "Sysctl support (/proc/sys)" if EXPERT
depends on PROC_FS
- select SYSCTL
default y
help
The sysctl interface provides a means of dynamically changing
diff --git a/fs/proc/Makefile b/fs/proc/Makefile
index 8bc615ff84e5..27e2b26b2d42 100644
--- a/fs/proc/Makefile
+++ b/fs/proc/Makefile
@@ -29,7 +29,7 @@ proc-y += softirqs.o
proc-y += namespaces.o
proc-y += self.o
proc-y += thread_self.o
-proc-$(CONFIG_PROC_SYSCTL) += proc_sysctl.o
+proc-$(CONFIG_SYSCTL) += proc_sysctl.o
proc-$(CONFIG_NET) += proc_net.o
proc-$(CONFIG_PROC_KCORE) += kcore.o
proc-$(CONFIG_PROC_VMCORE) += vmcore.o
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index b232e1098117..04bd6c9e65a7 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -351,7 +351,7 @@ extern void proc_thread_self_init(void);
/*
* proc_sysctl.c
*/
-#ifdef CONFIG_PROC_SYSCTL
+#ifdef CONFIG_SYSCTL
extern int proc_sys_init(void);
extern void proc_sys_evict_inode(struct inode *inode,
struct ctl_table_header *head);
diff --git a/fs/quota/Kconfig b/fs/quota/Kconfig
index 818083a36bef..68eb441328ef 100644
--- a/fs/quota/Kconfig
+++ b/fs/quota/Kconfig
@@ -25,16 +25,6 @@ config QUOTA_NETLINK_INTERFACE
hardlimit, etc.) will be reported through netlink interface. If unsure,
say Y.
-config PRINT_QUOTA_WARNING
- bool "Print quota warnings to console (OBSOLETE)"
- depends on QUOTA && BROKEN
- default y
- help
- If you say Y here, quota warnings (about exceeding softlimit, reaching
- hardlimit, etc.) will be printed to the process' controlling terminal.
- Note that this behavior is currently deprecated and may go away in
- future. Please use notification via netlink socket instead.
-
config QUOTA_DEBUG
bool "Additional quota sanity checks"
depends on QUOTA
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 9850de3955d3..204afc5e984b 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -153,7 +153,7 @@ void __quota_error(struct super_block *sb, const char *func,
}
EXPORT_SYMBOL(__quota_error);
-#if defined(CONFIG_QUOTA_DEBUG) || defined(CONFIG_PRINT_QUOTA_WARNING)
+#ifdef CONFIG_QUOTA_DEBUG
static char *quotatypes[] = INITQFNAMES;
#endif
static struct quota_format_type *quota_formats; /* List of registered formats */
@@ -1208,72 +1208,6 @@ static int warning_issued(struct dquot *dquot, const int warntype)
return test_and_set_bit(flag, &dquot->dq_flags);
}
-#ifdef CONFIG_PRINT_QUOTA_WARNING
-static int flag_print_warnings = 1;
-
-static int need_print_warning(struct dquot_warn *warn)
-{
- if (!flag_print_warnings)
- return 0;
-
- switch (warn->w_dq_id.type) {
- case USRQUOTA:
- return uid_eq(current_fsuid(), warn->w_dq_id.uid);
- case GRPQUOTA:
- return in_group_p(warn->w_dq_id.gid);
- case PRJQUOTA:
- return 1;
- }
- return 0;
-}
-
-/* Print warning to user which exceeded quota */
-static void print_warning(struct dquot_warn *warn)
-{
- char *msg = NULL;
- struct tty_struct *tty;
- int warntype = warn->w_type;
-
- if (warntype == QUOTA_NL_IHARDBELOW ||
- warntype == QUOTA_NL_ISOFTBELOW ||
- warntype == QUOTA_NL_BHARDBELOW ||
- warntype == QUOTA_NL_BSOFTBELOW || !need_print_warning(warn))
- return;
-
- tty = get_current_tty();
- if (!tty)
- return;
- tty_write_message(tty, warn->w_sb->s_id);
- if (warntype == QUOTA_NL_ISOFTWARN || warntype == QUOTA_NL_BSOFTWARN)
- tty_write_message(tty, ": warning, ");
- else
- tty_write_message(tty, ": write failed, ");
- tty_write_message(tty, quotatypes[warn->w_dq_id.type]);
- switch (warntype) {
- case QUOTA_NL_IHARDWARN:
- msg = " file limit reached.\r\n";
- break;
- case QUOTA_NL_ISOFTLONGWARN:
- msg = " file quota exceeded too long.\r\n";
- break;
- case QUOTA_NL_ISOFTWARN:
- msg = " file quota exceeded.\r\n";
- break;
- case QUOTA_NL_BHARDWARN:
- msg = " block limit reached.\r\n";
- break;
- case QUOTA_NL_BSOFTLONGWARN:
- msg = " block quota exceeded too long.\r\n";
- break;
- case QUOTA_NL_BSOFTWARN:
- msg = " block quota exceeded.\r\n";
- break;
- }
- tty_write_message(tty, msg);
- tty_kref_put(tty);
-}
-#endif
-
static void prepare_warning(struct dquot_warn *warn, struct dquot *dquot,
int warntype)
{
@@ -1296,9 +1230,7 @@ static void flush_warnings(struct dquot_warn *warn)
for (i = 0; i < MAXQUOTAS; i++) {
if (warn[i].w_type == QUOTA_NL_NOWARN)
continue;
-#ifdef CONFIG_PRINT_QUOTA_WARNING
- print_warning(&warn[i]);
-#endif
+
quota_send_warning(warn[i].w_dq_id,
warn[i].w_sb->s_dev, warn[i].w_type);
}
@@ -3008,15 +2940,6 @@ static const struct ctl_table fs_dqstats_table[] = {
.mode = 0444,
.proc_handler = do_proc_dqstats,
},
-#ifdef CONFIG_PRINT_QUOTA_WARNING
- {
- .procname = "warnings",
- .data = &flag_print_warnings,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = proc_dointvec,
- },
-#endif
};
static int __init dquot_init(void)
diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c
index 2b29fb5a8702..18ec9f564b5a 100644
--- a/fs/resctrl/ctrlmondata.c
+++ b/fs/resctrl/ctrlmondata.c
@@ -312,17 +312,20 @@ ssize_t rdtgroup_schemata_write(struct kernfs_open_file *of,
char *tok, *resname;
int ret = 0;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
- buf[nbytes - 1] = '\0';
-
rdtgrp = rdtgroup_kn_lock_live(of->kn);
if (!rdtgrp) {
rdtgroup_kn_unlock(of->kn);
return -ENOENT;
}
- rdt_last_cmd_clear();
+
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("schemata: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
/*
* No changes to pseudo-locked region allowed. It has to be removed
@@ -331,7 +334,7 @@ ssize_t rdtgroup_schemata_write(struct kernfs_open_file *of,
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
ret = -EINVAL;
rdt_last_cmd_puts("Resource group is pseudo-locked\n");
- goto out;
+ goto out_unlock;
}
rdt_staged_configs_clear();
@@ -341,16 +344,16 @@ ssize_t rdtgroup_schemata_write(struct kernfs_open_file *of,
if (!tok) {
rdt_last_cmd_puts("Missing ':'\n");
ret = -EINVAL;
- goto out;
+ goto out_clear_staged;
}
if (tok[0] == '\0') {
rdt_last_cmd_printf("Missing '%s' value\n", resname);
ret = -EINVAL;
- goto out;
+ goto out_clear_staged;
}
ret = rdtgroup_parse_resource(resname, tok, rdtgrp);
if (ret)
- goto out;
+ goto out_clear_staged;
}
list_for_each_entry(s, &resctrl_schema_all, list) {
@@ -365,7 +368,7 @@ ssize_t rdtgroup_schemata_write(struct kernfs_open_file *of,
ret = resctrl_arch_update_domains(r, rdtgrp->closid);
if (ret)
- goto out;
+ goto out_clear_staged;
}
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP) {
@@ -378,8 +381,9 @@ ssize_t rdtgroup_schemata_write(struct kernfs_open_file *of,
ret = rdtgroup_pseudo_lock_create(rdtgrp);
}
-out:
+out_clear_staged:
rdt_staged_configs_clear();
+out_unlock:
rdtgroup_kn_unlock(of->kn);
return ret ?: nbytes;
}
@@ -429,7 +433,6 @@ int rdtgroup_schemata_show(struct kernfs_open_file *of,
}
} else if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
if (!rdtgrp->plr->d) {
- rdt_last_cmd_clear();
rdt_last_cmd_puts("Cache domain offline\n");
ret = -ENODEV;
} else {
@@ -465,17 +468,20 @@ ssize_t rdtgroup_mba_mbps_event_write(struct kernfs_open_file *of,
struct rdtgroup *rdtgrp;
int ret = 0;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
- buf[nbytes - 1] = '\0';
-
rdtgrp = rdtgroup_kn_lock_live(of->kn);
if (!rdtgrp) {
rdtgroup_kn_unlock(of->kn);
return -ENOENT;
}
- rdt_last_cmd_clear();
+
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mba_MBps_event: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
if (!strcmp(buf, "mbm_local_bytes")) {
if (resctrl_is_mon_event_enabled(QOS_L3_MBM_LOCAL_EVENT_ID))
@@ -494,6 +500,7 @@ ssize_t rdtgroup_mba_mbps_event_write(struct kernfs_open_file *of,
if (ret)
rdt_last_cmd_printf("Unsupported event id '%s'\n", buf);
+out_unlock:
rdtgroup_kn_unlock(of->kn);
return ret ?: nbytes;
@@ -857,16 +864,18 @@ ssize_t resctrl_io_alloc_write(struct kernfs_open_file *of, char *buf,
bool enable;
int ret;
- ret = kstrtobool(buf, &enable);
- if (ret)
- return ret;
-
if (!info_kn_lock(of->kn))
return -ENOENT;
r = s->res;
rdt_last_cmd_clear();
+ ret = kstrtobool(buf, &enable);
+ if (ret) {
+ rdt_last_cmd_puts("io_alloc: Invalid input\n");
+ goto out_unlock;
+ }
+
if (!r->cache.io_alloc_capable) {
rdt_last_cmd_printf("io_alloc is not supported on %s\n", s->name);
ret = -ENODEV;
@@ -1021,17 +1030,21 @@ ssize_t resctrl_io_alloc_cbm_write(struct kernfs_open_file *of, char *buf,
u32 io_alloc_closid;
int ret = 0;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
-
- buf[nbytes - 1] = '\0';
-
if (!info_kn_lock(of->kn))
return -ENOENT;
rdt_last_cmd_clear();
r = s->res;
+
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("io_alloc_cbm: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
+
if (!r->cache.io_alloc_capable) {
rdt_last_cmd_printf("io_alloc is not supported on %s\n", s->name);
ret = -ENODEV;
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 7340b1d17f17..73413cb128ea 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -144,8 +144,8 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
arch_priv = mon_event_all[QOS_L3_OCCUP_EVENT_ID].arch_priv;
arch_mon_ctx = resctrl_arch_mon_ctx_alloc(r, QOS_L3_OCCUP_EVENT_ID);
if (IS_ERR(arch_mon_ctx)) {
- pr_warn_ratelimited("Failed to allocate monitor context: %ld",
- PTR_ERR(arch_mon_ctx));
+ pr_warn_ratelimited("Failed to allocate monitor context: %pe",
+ arch_mon_ctx);
return;
}
@@ -458,8 +458,10 @@ static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
}
/* Reading a single domain, must be on a CPU in that domain. */
- if (!cpumask_test_cpu(cpu, &d->hdr.cpu_mask))
+ if (!cpumask_test_cpu(cpu, &d->hdr.cpu_mask)) {
+ rr->err = -EIO;
return -EINVAL;
+ }
if (rr->is_mbm_cntr)
rr->err = resctrl_arch_cntr_read(rr->r, d, closid, rmid, cntr_id,
rr->evt->evtid, &tval);
@@ -496,8 +498,10 @@ static int __l3_mon_event_count_sum(struct rdtgroup *rdtgrp, struct rmid_read *r
}
/* Summing domains that share a cache, must be on a CPU for that cache. */
- if (!cpumask_test_cpu(cpu, &rr->ci->shared_cpu_map))
+ if (!cpumask_test_cpu(cpu, &rr->ci->shared_cpu_map)) {
+ rr->err = -EIO;
return -EINVAL;
+ }
/*
* Legacy files must report the sum of an event across all
@@ -771,8 +775,8 @@ static void mbm_update_one_event(struct rdt_resource *r, struct rdt_l3_mon_domai
} else {
rr.arch_mon_ctx = resctrl_arch_mon_ctx_alloc(rr.r, evtid);
if (IS_ERR(rr.arch_mon_ctx)) {
- pr_warn_ratelimited("Failed to allocate monitor context: %ld",
- PTR_ERR(rr.arch_mon_ctx));
+ pr_warn_ratelimited("Failed to allocate monitor context: %pe",
+ rr.arch_mon_ctx);
return;
}
}
@@ -1160,14 +1164,16 @@ ssize_t resctrl_mbm_assign_on_mkdir_write(struct kernfs_open_file *of, char *buf
bool value;
int ret;
- ret = kstrtobool(buf, &value);
- if (ret)
- return ret;
-
if (!info_kn_lock(of->kn))
return -ENOENT;
rdt_last_cmd_clear();
+ ret = kstrtobool(buf, &value);
+ if (ret) {
+ rdt_last_cmd_puts("mbm_assign_on_mkdir: Invalid input\n");
+ goto out_unlock;
+ }
+
if (!resctrl_arch_mbm_cntr_assign_enabled(r)) {
rdt_last_cmd_puts("mbm_event counter assignment mode is not enabled\n");
ret = -EINVAL;
@@ -1461,17 +1467,20 @@ ssize_t event_filter_write(struct kernfs_open_file *of, char *buf, size_t nbytes
u32 evt_cfg = 0;
int ret = 0;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
-
- buf[nbytes - 1] = '\0';
-
if (!info_kn_lock(of->kn))
return -ENOENT;
rdt_last_cmd_clear();
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("event_filter: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
+
r = resctrl_arch_get_resource(mevt->rid);
if (!resctrl_arch_mbm_cntr_assign_enabled(r)) {
rdt_last_cmd_puts("mbm_event counter assignment mode is not enabled\n");
@@ -1535,17 +1544,20 @@ ssize_t resctrl_mbm_assign_mode_write(struct kernfs_open_file *of, char *buf,
int ret = 0;
bool enable;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
-
- buf[nbytes - 1] = '\0';
-
if (!info_kn_lock(of->kn))
return -ENOENT;
rdt_last_cmd_clear();
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mbm_assign_mode: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
+
if (!strcmp(buf, "default")) {
enable = 0;
} else if (!strcmp(buf, "mbm_event")) {
@@ -1684,7 +1696,6 @@ int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, voi
goto out_unlock;
}
- rdt_last_cmd_clear();
if (!resctrl_arch_mbm_cntr_assign_enabled(r)) {
rdt_last_cmd_puts("mbm_event counter assignment mode is not enabled\n");
ret = -EINVAL;
@@ -1819,23 +1830,25 @@ ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
char *token, *event;
int ret = 0;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
-
- buf[nbytes - 1] = '\0';
-
rdtgrp = rdtgroup_kn_lock_live(of->kn);
if (!rdtgrp) {
rdtgroup_kn_unlock(of->kn);
return -ENOENT;
}
- rdt_last_cmd_clear();
+
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mbm_L3_assignments: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ buf[nbytes - 1] = '\0';
if (!resctrl_arch_mbm_cntr_assign_enabled(r)) {
rdt_last_cmd_puts("mbm_event mode is not enabled\n");
- rdtgroup_kn_unlock(of->kn);
- return -EINVAL;
+ ret = -EINVAL;
+ goto out_unlock;
}
while ((token = strsep(&buf, "\n")) != NULL) {
@@ -1851,6 +1864,7 @@ ssize_t mbm_L3_assignments_write(struct kernfs_open_file *of, char *buf,
break;
}
+out_unlock:
rdtgroup_kn_unlock(of->kn);
return ret ?: nbytes;
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index 30df902ea776..5dcbb0a964e8 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -359,7 +359,6 @@ static int rdtgroup_cpus_show(struct kernfs_open_file *of,
if (rdtgrp) {
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
if (!rdtgrp->plr->d) {
- rdt_last_cmd_clear();
rdt_last_cmd_puts("Cache domain offline\n");
ret = -ENODEV;
} else {
@@ -511,38 +510,38 @@ static int cpus_ctrl_write(struct rdtgroup *rdtgrp, cpumask_var_t newmask,
static ssize_t rdtgroup_cpus_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
- cpumask_var_t tmpmask, newmask, tmpmask1;
+ cpumask_var_t tmpmask = CPUMASK_VAR_NULL, newmask = CPUMASK_VAR_NULL;
+ cpumask_var_t tmpmask1 = CPUMASK_VAR_NULL;
struct rdtgroup *rdtgrp;
int ret;
- if (!buf)
- return -EINVAL;
-
- if (!zalloc_cpumask_var(&tmpmask, GFP_KERNEL))
- return -ENOMEM;
- if (!zalloc_cpumask_var(&newmask, GFP_KERNEL)) {
- free_cpumask_var(tmpmask);
- return -ENOMEM;
- }
- if (!zalloc_cpumask_var(&tmpmask1, GFP_KERNEL)) {
- free_cpumask_var(tmpmask);
- free_cpumask_var(newmask);
- return -ENOMEM;
- }
-
rdtgrp = rdtgroup_kn_lock_live(of->kn);
if (!rdtgrp) {
ret = -ENOENT;
- goto unlock;
+ goto out_unlock;
}
- rdt_last_cmd_clear();
+ if (!buf) {
+ rdt_last_cmd_printf("%s: Invalid input\n",
+ is_cpu_list(of) ? "cpus_list" : "cpus");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (!zalloc_cpumask_var(&tmpmask, GFP_KERNEL) ||
+ !zalloc_cpumask_var(&newmask, GFP_KERNEL) ||
+ !zalloc_cpumask_var(&tmpmask1, GFP_KERNEL)) {
+ rdt_last_cmd_printf("%s: Kernel allocation failure\n",
+ is_cpu_list(of) ? "cpus_list" : "cpus");
+ ret = -ENOMEM;
+ goto out_free;
+ }
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED ||
rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP) {
ret = -EINVAL;
rdt_last_cmd_puts("Pseudo-locking in progress\n");
- goto unlock;
+ goto out_free;
}
if (is_cpu_list(of))
@@ -552,7 +551,7 @@ static ssize_t rdtgroup_cpus_write(struct kernfs_open_file *of,
if (ret) {
rdt_last_cmd_puts("Bad CPU list/mask\n");
- goto unlock;
+ goto out_free;
}
/* check that user didn't specify any offline cpus */
@@ -560,7 +559,7 @@ static ssize_t rdtgroup_cpus_write(struct kernfs_open_file *of,
if (!cpumask_empty(tmpmask)) {
ret = -EINVAL;
rdt_last_cmd_puts("Can only assign online CPUs\n");
- goto unlock;
+ goto out_free;
}
if (rdtgrp->type == RDTCTRL_GROUP)
@@ -570,11 +569,12 @@ static ssize_t rdtgroup_cpus_write(struct kernfs_open_file *of,
else
ret = -EINVAL;
-unlock:
- rdtgroup_kn_unlock(of->kn);
+out_free:
free_cpumask_var(tmpmask);
free_cpumask_var(newmask);
free_cpumask_var(tmpmask1);
+out_unlock:
+ rdtgroup_kn_unlock(of->kn);
return ret ?: nbytes;
}
@@ -785,7 +785,6 @@ static ssize_t rdtgroup_tasks_write(struct kernfs_open_file *of,
rdtgroup_kn_unlock(of->kn);
return -ENOENT;
}
- rdt_last_cmd_clear();
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED ||
rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP) {
@@ -980,10 +979,13 @@ static int rdt_last_cmd_status_show(struct kernfs_open_file *of,
if (!info_kn_lock(of->kn))
return -ENOENT;
len = seq_buf_used(&last_cmd_status);
- if (len)
+ if (len) {
seq_printf(seq, "%.*s", len, last_cmd_status_buf);
- else
+ if (seq_buf_has_overflowed(&last_cmd_status))
+ seq_puts(seq, "[truncated]\n");
+ } else {
seq_puts(seq, "ok\n");
+ }
info_kn_unlock(of->kn);
return 0;
}
@@ -1296,14 +1298,20 @@ static ssize_t max_threshold_occ_write(struct kernfs_open_file *of,
unsigned int bytes;
int ret;
- ret = kstrtouint(buf, 0, &bytes);
- if (ret)
- return ret;
-
if (!info_kn_lock(of->kn))
return -ENOENT;
+ rdt_last_cmd_clear();
+
+ ret = kstrtouint(buf, 0, &bytes);
+ if (ret) {
+ rdt_last_cmd_puts("max_threshold_occupancy: Invalid input\n");
+ goto out_unlock;
+ }
+
if (bytes > resctrl_rmid_realloc_limit) {
+ rdt_last_cmd_printf("max_threshold_occupancy: Exceeds limit (before adjustment) of %u bytes\n",
+ resctrl_rmid_realloc_limit);
ret = -EINVAL;
goto out_unlock;
}
@@ -1514,18 +1522,20 @@ static ssize_t rdtgroup_mode_write(struct kernfs_open_file *of,
enum rdtgrp_mode mode;
int ret = 0;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
- buf[nbytes - 1] = '\0';
-
rdtgrp = rdtgroup_kn_lock_live(of->kn);
if (!rdtgrp) {
rdtgroup_kn_unlock(of->kn);
return -ENOENT;
}
- rdt_last_cmd_clear();
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mode: Invalid input\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ buf[nbytes - 1] = '\0';
mode = rdtgrp->mode;
@@ -1654,7 +1664,6 @@ static int rdtgroup_size_show(struct kernfs_open_file *of,
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
if (!rdtgrp->plr->d) {
- rdt_last_cmd_clear();
rdt_last_cmd_puts("Cache domain offline\n");
ret = -ENODEV;
} else {
@@ -1856,19 +1865,23 @@ static ssize_t mbm_total_bytes_config_write(struct kernfs_open_file *of,
struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
int ret;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
-
if (!info_kn_lock(of->kn))
return -ENOENT;
rdt_last_cmd_clear();
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mbm_total_bytes_config: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
buf[nbytes - 1] = '\0';
ret = mon_config_write(r, buf, QOS_L3_MBM_TOTAL_EVENT_ID);
+out_unlock:
info_kn_unlock(of->kn);
return ret ?: nbytes;
@@ -1881,19 +1894,23 @@ static ssize_t mbm_local_bytes_config_write(struct kernfs_open_file *of,
struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
int ret;
- /* Valid input requires a trailing newline */
- if (nbytes == 0 || buf[nbytes - 1] != '\n')
- return -EINVAL;
-
if (!info_kn_lock(of->kn))
return -ENOENT;
rdt_last_cmd_clear();
+ /* Valid input requires a trailing newline */
+ if (nbytes == 0 || buf[nbytes - 1] != '\n') {
+ rdt_last_cmd_puts("mbm_local_bytes_config: Invalid input\n");
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
buf[nbytes - 1] = '\0';
ret = mon_config_write(r, buf, QOS_L3_MBM_LOCAL_EVENT_ID);
+out_unlock:
info_kn_unlock(of->kn);
return ret ?: nbytes;
@@ -2706,10 +2723,20 @@ struct rdtgroup *rdtgroup_kn_lock_live(struct kernfs_node *kn)
cpus_read_lock();
mutex_lock(&rdtgroup_mutex);
+ rdt_last_cmd_clear();
/* Was this group deleted while we waited? */
- if (rdtgrp->flags & RDT_DELETED)
+ if (rdtgrp->flags & RDT_DELETED) {
+ /*
+ * It is safe to dereference kn to obtain the resource group's
+ * name because one extra reference to kn is obtained
+ * during resource group creation that will be released by
+ * rdtgroup_remove() called by rdtgroup_kn_put().
+ */
+ rdt_last_cmd_printf("Resource group %s deleted. No commands possible.\n",
+ rdt_kn_name(rdtgrp->kn));
return NULL;
+ }
return rdtgrp;
}
@@ -3923,8 +3950,6 @@ static int mkdir_rdt_prepare(struct kernfs_node *parent_kn,
goto out_unlock;
}
- rdt_last_cmd_clear();
-
/*
* Check that the parent directory for a monitor group is a "mon_groups"
* directory.
diff --git a/fs/seq_file.c b/fs/seq_file.c
index 4745db2a34d1..456c78719fd0 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -428,7 +428,7 @@ EXPORT_SYMBOL(seq_bprintf);
#endif /* CONFIG_BINARY_PRINTF */
/**
- * mangle_path - mangle and copy path to buffer beginning
+ * seq_mangle_path - mangle and copy path to buffer beginning
* @s: buffer start
* @p: beginning of path in above buffer
* @esc: set of characters that need escaping
@@ -438,7 +438,7 @@ EXPORT_SYMBOL(seq_bprintf);
* Returns pointer past last written character in @s, or NULL in case of
* failure.
*/
-char *mangle_path(char *s, const char *p, const char *esc)
+char *seq_mangle_path(char *s, const char *p, const char *esc)
{
while (s <= p) {
char c = *p++;
@@ -457,7 +457,6 @@ char *mangle_path(char *s, const char *p, const char *esc)
}
return NULL;
}
-EXPORT_SYMBOL(mangle_path);
/**
* seq_path - seq_file interface to print a pathname
@@ -477,7 +476,7 @@ int seq_path(struct seq_file *m, const struct path *path, const char *esc)
if (size) {
char *p = d_path(path, buf, size);
if (!IS_ERR(p)) {
- char *end = mangle_path(buf, p, esc);
+ char *end = seq_mangle_path(buf, p, esc);
if (end)
res = end - buf;
}
@@ -520,7 +519,7 @@ int seq_path_root(struct seq_file *m, const struct path *path,
return SEQ_SKIP;
res = PTR_ERR(p);
if (!IS_ERR(p)) {
- char *end = mangle_path(buf, p, esc);
+ char *end = seq_mangle_path(buf, p, esc);
if (end)
res = end - buf;
else
@@ -544,7 +543,7 @@ int seq_dentry(struct seq_file *m, struct dentry *dentry, const char *esc)
if (size) {
char *p = dentry_path(dentry, buf, size);
if (!IS_ERR(p)) {
- char *end = mangle_path(buf, p, esc);
+ char *end = seq_mangle_path(buf, p, esc);
if (end)
res = end - buf;
}
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index 7f2924ce2881..8e6b19f22ef5 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -915,6 +915,14 @@ void _cifsFileInfo_put(struct cifsFileInfo *cifs_file,
cifs_set_oplock_level(cifsi, 0);
}
+ if (OPEN_FMODE(cifs_file->f_flags) & FMODE_WRITE) {
+ /* Stamp while open_file_lock is held; covers all close paths
+ * including background I/O. Pairs with smp_load_acquire() in
+ * is_size_safe_to_change().
+ */
+ smp_store_release(&cifsi->time_last_write, jiffies);
+ }
+
spin_unlock(&cifsi->open_file_lock);
spin_unlock(&tcon->open_file_lock);
@@ -1429,15 +1437,6 @@ void smb2_deferred_work_close(struct work_struct *work)
cifs_del_deferred_close(cfile);
cfile->deferred_close_scheduled = false;
spin_unlock(&cinode->deferred_lock);
- /*
- * Refresh time_last_write immediately before the actual server close
- * so the protection window is anchored to the real close time, not
- * the earlier userspace close time stored by cifs_close().
- */
- if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&cinode->time_last_write, jiffies);
- }
_cifsFileInfo_put(cfile, true, false);
}
@@ -1467,10 +1466,6 @@ int cifs_close(struct inode *inode, struct file *file)
if (file->private_data != NULL) {
cfile = file->private_data;
file->private_data = NULL;
- if (file->f_mode & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&cinode->time_last_write, jiffies);
- }
dclose = kmalloc_obj(struct cifs_deferred_close);
if ((cfile->status_file_deleted == false) &&
(smb2_can_defer_close(inode, dclose))) {
@@ -3276,13 +3271,13 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file,
* No writable handles open. Check whether we are within the attribute
* cache validity window of a recent local modification.
*
- * For the close() path: cifs_close() calls smp_store_release() on
- * time_last_write before _cifsFileInfo_put() removes the handle under
- * open_file_lock. That spin_unlock() is a store-release that pairs
- * with the spin_lock() (load-acquire) in is_inode_writable() above,
- * so if is_inode_writable() returned false the smp_load_acquire()
- * below is guaranteed to observe any time_last_write update from a
- * concurrent close().
+ * For the close() path: _cifsFileInfo_put() stamps time_last_write
+ * (via smp_store_release()) before releasing open_file_lock. That
+ * spin_unlock() is a store-release that pairs with the spin_lock()
+ * (load-acquire) in is_inode_writable() above, so if
+ * is_inode_writable() returned false the smp_load_acquire() below is
+ * guaranteed to observe any time_last_write update from a concurrent
+ * close(), covering all close paths including background I/O.
*
* For the setattr/truncate paths: those callers use smp_store_release()
* directly; the smp_load_acquire() below pairs with that store. There
@@ -3297,7 +3292,7 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file,
* jiffies is still close to INITIAL_JIFFIES on 32-bit systems.
*/
if (from_readdir) {
- /* Pairs with smp_store_release() at close and truncate sites. */
+ /* Pairs with smp_store_release() in _cifsFileInfo_put() and setattr. */
tlw = smp_load_acquire(&cifsInode->time_last_write);
if (tlw && time_before(jiffies, tlw + cifs_sb->ctx->acregmax))
return false;
diff --git a/fs/smb/client/fscache.c b/fs/smb/client/fscache.c
index 01424a5cdb99..9f66bbcd3801 100644
--- a/fs/smb/client/fscache.c
+++ b/fs/smb/client/fscache.c
@@ -38,7 +38,6 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)
struct TCP_Server_Info *server = tcon->ses->server;
struct fscache_volume *vcookie;
const struct sockaddr *sa = (struct sockaddr *)&server->dstaddr;
- size_t slen, i;
char *sharename;
char *key;
int ret = -ENOMEM;
@@ -73,10 +72,7 @@ int cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)
return PTR_ERR(sharename);
}
- slen = strlen(sharename);
- for (i = 0; i < slen; i++)
- if (sharename[i] == '/')
- sharename[i] = ';';
+ strreplace(sharename, '/', ';');
key = kasprintf(GFP_KERNEL, "cifs,%pISpc,%s", sa, sharename);
if (!key)
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index dc64474d16f6..18f562ac172e 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -3066,6 +3066,7 @@ void cifs_setsize(struct inode *inode, loff_t offset)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
truncate_pagecache(inode, offset);
netfs_wait_for_outstanding_io(inode);
+ fscache_resize_cookie(cifs_inode_cookie(inode), offset);
}
int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
@@ -3197,6 +3198,17 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
rc = 0;
if (attrs->ia_valid & ATTR_SIZE) {
+ if (attrs->ia_size != i_size_read(inode)) {
+ /* Stamp before RPC. On failure the stamp remains: restoring a
+ * stale snapshot could silently erase a concurrent
+ * _cifsFileInfo_put() close stamp. readdir is suppressed
+ * until the stamp expires; stat() bypasses this via the
+ * from_readdir=false path in is_size_safe_to_change() and
+ * always returns an authoritative QUERY_INFO result.
+ * Pairs with smp_load_acquire() in is_size_safe_to_change().
+ */
+ smp_store_release(&cifsInode->time_last_write, jiffies);
+ }
rc = cifs_file_set_size(xid, direntry, full_path,
open_file, attrs->ia_size);
if (rc != 0)
@@ -3286,8 +3298,6 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
if ((attrs->ia_valid & ATTR_SIZE) &&
attrs->ia_size != i_size_read(inode)) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&cifsInode->time_last_write, jiffies);
truncate_setsize(inode, attrs->ia_size);
netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true);
fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size);
@@ -3377,6 +3387,17 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
}
if (attrs->ia_valid & ATTR_SIZE) {
+ if (attrs->ia_size != i_size_read(inode)) {
+ /* Stamp before RPC. On failure the stamp remains: restoring a
+ * stale snapshot could silently erase a concurrent
+ * _cifsFileInfo_put() close stamp. readdir is suppressed
+ * until the stamp expires; stat() bypasses this via the
+ * from_readdir=false path in is_size_safe_to_change() and
+ * always returns an authoritative QUERY_INFO result.
+ * Pairs with smp_load_acquire() in is_size_safe_to_change().
+ */
+ smp_store_release(&cifsInode->time_last_write, jiffies);
+ }
rc = cifs_file_set_size(xid, direntry, full_path,
cfile, attrs->ia_size);
if (rc != 0)
@@ -3489,8 +3510,6 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
if ((attrs->ia_valid & ATTR_SIZE) &&
attrs->ia_size != i_size_read(inode)) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&cifsInode->time_last_write, jiffies);
truncate_setsize(inode, attrs->ia_size);
netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true);
fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size);
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 6edebc0807ea..46e1382e8e04 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -525,24 +525,11 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
}
spin_unlock(&cifs_inode->open_file_lock);
- if (failed_cfile) {
- if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write,
- jiffies);
- }
+ if (failed_cfile)
_cifsFileInfo_put(failed_cfile, false, false);
- }
list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
- struct cifsFileInfo *cfile = tmp_list->cfile;
-
- if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write,
- jiffies);
- }
- _cifsFileInfo_put(cfile, false, false);
+ _cifsFileInfo_put(tmp_list->cfile, false, false);
list_del(&tmp_list->list);
kfree(tmp_list);
}
@@ -576,24 +563,11 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon)
}
spin_unlock(&tcon->open_file_lock);
- if (failed_cfile) {
- if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write,
- jiffies);
- }
+ if (failed_cfile)
_cifsFileInfo_put(failed_cfile, true, false);
- }
list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
- struct cifsFileInfo *cfile = tmp_list->cfile;
-
- if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write,
- jiffies);
- }
- _cifsFileInfo_put(cfile, true, false);
+ _cifsFileInfo_put(tmp_list->cfile, true, false);
list_del(&tmp_list->list);
kfree(tmp_list);
}
@@ -663,24 +637,11 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
}
spin_unlock(&tcon->open_file_lock);
- if (failed_cfile) {
- if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write,
- jiffies);
- }
+ if (failed_cfile)
_cifsFileInfo_put(failed_cfile, true, false);
- }
list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
- struct cifsFileInfo *cfile = tmp_list->cfile;
-
- if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) {
- /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */
- smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write,
- jiffies);
- }
- _cifsFileInfo_put(cfile, true, false);
+ _cifsFileInfo_put(tmp_list->cfile, true, false);
list_del(&tmp_list->list);
kfree(tmp_list);
}
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index cbd51a08e97e..192649fec25d 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -4745,10 +4745,10 @@ smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst,
size_t cur_size = 0;
rc = netfs_alloc_folioq_buffer(NULL, &buffer, &cur_size,
size, GFP_NOFS);
+ new->rq_buffer = buffer;
if (rc < 0)
goto err_free;
- new->rq_buffer = buffer;
iov_iter_folio_queue(&new->rq_iter, ITER_SOURCE,
buffer, 0, 0, size);
diff --git a/fs/stat.c b/fs/stat.c
index 89909746bed1..c461c3054234 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -53,7 +53,7 @@ void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
}
stat->mtime = inode_get_mtime(inode);
- stat->ctime.tv_sec = inode->i_ctime_sec;
+ stat->ctime.tv_sec = inode_get_ctime_sec(inode);
stat->ctime.tv_nsec = (u32)atomic_read(pcn);
if (!(stat->ctime.tv_nsec & I_CTIME_QUERIED))
stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn));
diff --git a/fs/udf/dir.c b/fs/udf/dir.c
index ebc9f6a379fe..425e3c162935 100644
--- a/fs/udf/dir.c
+++ b/fs/udf/dir.c
@@ -157,6 +157,6 @@ const struct file_operations udf_dir_operations = {
.read = generic_read_dir,
.iterate_shared = udf_readdir,
.unlocked_ioctl = udf_ioctl,
- .fsync = udf_fsync,
+ .fsync = simple_fsync,
.setlease = generic_setlease,
};
diff --git a/fs/udf/file.c b/fs/udf/file.c
index f7f1422de30f..57d11606a2a7 100644
--- a/fs/udf/file.c
+++ b/fs/udf/file.c
@@ -198,13 +198,6 @@ static int udf_file_mmap(struct file *file, struct vm_area_struct *vma)
return 0;
}
-int udf_fsync(struct file *file, loff_t start, loff_t end, int datasync)
-{
- return mmb_fsync(file,
- &UDF_I(file->f_mapping->host)->i_metadata_bhs,
- start, end, datasync);
-}
-
const struct file_operations udf_file_operations = {
.read_iter = generic_file_read_iter,
.unlocked_ioctl = udf_ioctl,
@@ -212,7 +205,7 @@ const struct file_operations udf_file_operations = {
.mmap = udf_file_mmap,
.write_iter = udf_file_write_iter,
.release = udf_release_file,
- .fsync = udf_fsync,
+ .fsync = simple_fsync,
.splice_read = filemap_splice_read,
.splice_write = iter_file_splice_write,
.llseek = generic_file_llseek,
@@ -253,6 +246,8 @@ static int udf_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
setattr_copy(&nop_mnt_idmap, inode, attr);
mark_inode_dirty(inode);
+ if (IS_SYNC(inode))
+ sync_inode_metadata(inode, 1);
return 0;
}
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index c97914aa8d8b..e45e546a739a 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -51,8 +51,6 @@
struct udf_map_rq;
static umode_t udf_convert_permissions(struct fileEntry *);
-static int udf_update_inode(struct inode *, int);
-static int udf_sync_inode(struct inode *inode);
static int udf_alloc_i_data(struct inode *inode, size_t size);
static int inode_getblk(struct inode *inode, struct udf_map_rq *map);
static int udf_insert_aext(struct inode *, struct extent_position,
@@ -142,7 +140,7 @@ void udf_evict_inode(struct inode *inode)
if (!inode->i_nlink) {
want_delete = 1;
udf_setsize(inode, 0);
- udf_update_inode(inode, IS_SYNC(inode));
+ sync_inode_metadata(inode, IS_SYNC(inode));
}
if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB &&
inode->i_size != iinfo->i_lenExtents) {
@@ -336,65 +334,6 @@ const struct address_space_operations udf_aops = {
.migrate_folio = buffer_migrate_folio,
};
-/*
- * Expand file stored in ICB to a normal one-block-file
- *
- * This function requires i_mutex held
- */
-int udf_expand_file_adinicb(struct inode *inode)
-{
- struct folio *folio;
- struct udf_inode_info *iinfo = UDF_I(inode);
- int err;
-
- WARN_ON_ONCE(!inode_is_locked(inode));
- if (!iinfo->i_lenAlloc) {
- down_write(&iinfo->i_data_sem);
- if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
- iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
- else
- iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
- up_write(&iinfo->i_data_sem);
- mark_inode_dirty(inode);
- return 0;
- }
-
- folio = __filemap_get_folio(inode->i_mapping, 0,
- FGP_LOCK | FGP_ACCESSED | FGP_CREAT, GFP_KERNEL);
- if (IS_ERR(folio))
- return PTR_ERR(folio);
-
- if (!folio_test_uptodate(folio))
- udf_adinicb_read_folio(folio);
- down_write(&iinfo->i_data_sem);
- memset(iinfo->i_data + iinfo->i_lenEAttr, 0x00,
- iinfo->i_lenAlloc);
- iinfo->i_lenAlloc = 0;
- if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
- iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
- else
- iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
- folio_mark_dirty(folio);
- folio_unlock(folio);
- up_write(&iinfo->i_data_sem);
- err = filemap_fdatawrite(inode->i_mapping);
- if (err) {
- /* Restore everything back so that we don't lose data... */
- folio_lock(folio);
- down_write(&iinfo->i_data_sem);
- memcpy_from_folio(iinfo->i_data + iinfo->i_lenEAttr,
- folio, 0, inode->i_size);
- folio_unlock(folio);
- iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB;
- iinfo->i_lenAlloc = inode->i_size;
- up_write(&iinfo->i_data_sem);
- }
- folio_put(folio);
- mark_inode_dirty(inode);
-
- return err;
-}
-
#define UDF_MAP_CREATE 0x01 /* Mapping can allocate new blocks */
#define UDF_MAP_NOPREALLOC 0x02 /* Do not preallocate blocks */
@@ -455,6 +394,76 @@ out_read:
return ret;
}
+/*
+ * Expand file stored in ICB to a normal one-block-file
+ *
+ * This function requires i_mutex held
+ */
+int udf_expand_file_adinicb(struct inode *inode)
+{
+ struct folio *folio;
+ struct udf_inode_info *iinfo = UDF_I(inode);
+ struct udf_map_rq map = {
+ .lblk = 0,
+ .iflags = UDF_MAP_CREATE,
+ };
+ int err;
+
+ WARN_ON_ONCE(!inode_is_locked(inode));
+ if (!iinfo->i_lenAlloc) {
+ down_write(&iinfo->i_data_sem);
+ if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
+ iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
+ else
+ iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
+ up_write(&iinfo->i_data_sem);
+ mark_inode_dirty(inode);
+ return 0;
+ }
+
+ folio = __filemap_get_folio(inode->i_mapping, 0,
+ FGP_LOCK | FGP_ACCESSED | FGP_CREAT, GFP_KERNEL);
+ if (IS_ERR(folio))
+ return PTR_ERR(folio);
+
+ if (!folio_test_uptodate(folio))
+ udf_adinicb_read_folio(folio);
+ down_write(&iinfo->i_data_sem);
+ memset(iinfo->i_data + iinfo->i_lenEAttr, 0x00,
+ iinfo->i_lenAlloc);
+ iinfo->i_lenAlloc = 0;
+ if (UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_USE_SHORT_AD))
+ iinfo->i_alloc_type = ICBTAG_FLAG_AD_SHORT;
+ else
+ iinfo->i_alloc_type = ICBTAG_FLAG_AD_LONG;
+ up_write(&iinfo->i_data_sem);
+
+ /* Allocate the block underlying the data */
+ err = udf_map_block(inode, &map);
+ if (err < 0)
+ goto restore;
+
+ folio_mark_dirty(folio);
+ folio_unlock(folio);
+ err = filemap_fdatawrite(inode->i_mapping);
+ if (err) {
+ /* Restore everything back so that we don't lose data... */
+ folio_lock(folio);
+restore:
+ down_write(&iinfo->i_data_sem);
+ memcpy_from_folio(iinfo->i_data + iinfo->i_lenEAttr,
+ folio, 0, inode->i_size);
+ iinfo->i_alloc_type = ICBTAG_FLAG_AD_IN_ICB;
+ iinfo->i_lenAlloc = inode->i_size;
+ up_write(&iinfo->i_data_sem);
+ folio_unlock(folio);
+ }
+ folio_put(folio);
+ mark_inode_dirty(inode);
+
+ return err;
+}
+
static int __udf_get_block(struct inode *inode, sector_t block,
struct buffer_head *bh_result, int flags)
{
@@ -531,7 +540,7 @@ static int udf_do_extend_file(struct inode *inode,
sb->s_blocksize - 1) & ~(sb->s_blocksize - 1));
iinfo->i_lenExtents =
(iinfo->i_lenExtents + sb->s_blocksize - 1) &
- ~(sb->s_blocksize - 1);
+ ~((u64)sb->s_blocksize - 1);
}
add = 0;
@@ -936,10 +945,7 @@ static int inode_getblk(struct inode *inode, struct udf_map_rq *map)
iinfo->i_next_alloc_goal = newblocknum + 1;
inode_set_ctime_current(inode);
- if (IS_SYNC(inode))
- udf_sync_inode(inode);
- else
- mark_inode_dirty(inode);
+ mark_inode_dirty(inode);
ret = 0;
out_free:
brelse(prev_epos.bh);
@@ -1326,10 +1332,7 @@ set_size:
}
update_time:
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
- if (IS_SYNC(inode))
- udf_sync_inode(inode);
- else
- mark_inode_dirty(inode);
+ mark_inode_dirty(inode);
return err;
}
@@ -1475,6 +1478,10 @@ reread:
iinfo->i_lenAlloc = le32_to_cpu(
((struct unallocSpaceEntry *)bh->b_data)->
lengthAllocDescs);
+ if (iinfo->i_lenAlloc > bs - sizeof(struct unallocSpaceEntry)) {
+ ret = -EFSCORRUPTED;
+ goto out;
+ }
ret = udf_alloc_i_data(inode, bs -
sizeof(struct unallocSpaceEntry));
if (ret)
@@ -1482,6 +1489,7 @@ reread:
memcpy(iinfo->i_data,
bh->b_data + sizeof(struct unallocSpaceEntry),
bs - sizeof(struct unallocSpaceEntry));
+ brelse(bh);
return 0;
}
@@ -1705,14 +1713,28 @@ void udf_update_extra_perms(struct inode *inode, umode_t mode)
iinfo->i_extraPerms |= FE_PERM_O_DELETE;
}
-int udf_write_inode(struct inode *inode, struct writeback_control *wbc)
+int udf_sync_inode_metadata(struct inode *inode, struct writeback_control *wbc)
{
- return udf_update_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
-}
+ struct buffer_head *bh;
+ int err = 0;
-static int udf_sync_inode(struct inode *inode)
-{
- return udf_update_inode(inode, 1);
+ bh = sb_getblk(inode->i_sb,
+ udf_get_lb_pblock(inode->i_sb,
+ &UDF_I(inode)->i_location, 0));
+ if (!bh)
+ return -EIO;
+
+ sync_dirty_buffer(bh);
+ if (buffer_write_io_error(bh)) {
+ udf_warn(inode->i_sb, "IO error syncing udf inode [%08llx]\n",
+ inode->i_ino);
+ err = -EIO;
+ goto out;
+ }
+ err = mmb_sync(&UDF_I(inode)->i_metadata_bhs);
+out:
+ brelse(bh);
+ return err;
}
static void udf_adjust_time(struct udf_inode_info *iinfo, struct timespec64 time)
@@ -1723,7 +1745,7 @@ static void udf_adjust_time(struct udf_inode_info *iinfo, struct timespec64 time
iinfo->i_crtime = time;
}
-static int udf_update_inode(struct inode *inode, int do_sync)
+int udf_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct buffer_head *bh = NULL;
struct fileEntry *fe;
@@ -1732,7 +1754,6 @@ static int udf_update_inode(struct inode *inode, int do_sync)
uint32_t udfperms;
uint16_t icbflags;
uint16_t crclen;
- int err = 0;
struct udf_sb_info *sbi = UDF_SB(inode->i_sb);
unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits;
struct udf_inode_info *iinfo = UDF_I(inode);
@@ -1937,17 +1958,10 @@ finish:
/* write the data blocks */
mark_buffer_dirty(bh);
- if (do_sync) {
- sync_dirty_buffer(bh);
- if (buffer_write_io_error(bh)) {
- udf_warn(inode->i_sb, "IO error syncing udf inode [%08llx]\n",
- inode->i_ino);
- err = -EIO;
- }
- }
brelse(bh);
+ set_inode_metadata_writeback(inode);
- return err;
+ return 0;
}
struct inode *__udf_iget(struct super_block *sb, struct kernel_lb_addr *ino,
diff --git a/fs/udf/super.c b/fs/udf/super.c
index 9686078bba64..2ba5973ef4dd 100644
--- a/fs/udf/super.c
+++ b/fs/udf/super.c
@@ -211,6 +211,7 @@ static const struct super_operations udf_sb_ops = {
.alloc_inode = udf_alloc_inode,
.free_inode = udf_free_in_core_inode,
.write_inode = udf_write_inode,
+ .sync_inode_metadata = udf_sync_inode_metadata,
.evict_inode = udf_evict_inode,
.put_super = udf_put_super,
.sync_fs = udf_sync_fs,
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h
index 21de6925fc68..7d5a1981434e 100644
--- a/fs/udf/udfdecl.h
+++ b/fs/udf/udfdecl.h
@@ -137,7 +137,6 @@ static inline unsigned int udf_dir_entry_len(struct fileIdentDesc *cfi)
/* file.c */
extern long udf_ioctl(struct file *, unsigned int, unsigned long);
-int udf_fsync(struct file *file, loff_t start, loff_t end, int datasync);
/* inode.c */
extern struct inode *__udf_iget(struct super_block *, struct kernel_lb_addr *,
@@ -158,6 +157,7 @@ extern struct buffer_head *udf_bread(struct inode *inode, udf_pblk_t block,
extern int udf_setsize(struct inode *, loff_t);
extern void udf_evict_inode(struct inode *);
extern int udf_write_inode(struct inode *, struct writeback_control *wbc);
+int udf_sync_inode_metadata(struct inode *, struct writeback_control *wbc);
extern int inode_bmap(struct inode *inode, sector_t block,
struct extent_position *pos, struct kernel_lb_addr *eloc,
uint32_t *elen, sector_t *offset, int8_t *etype);
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 21d67af781da..17b9d643e1a8 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -80,7 +80,7 @@ xfs_buf_stale(
spin_lock(&bp->b_lockref.lock);
atomic_set(&bp->b_lru_ref, 0);
- if (!__lockref_is_dead(&bp->b_lockref))
+ if (!lockref_is_dead(&bp->b_lockref))
list_lru_del_obj(&bp->b_target->bt_lru, &bp->b_lru);
spin_unlock(&bp->b_lockref.lock);
}
@@ -841,7 +841,7 @@ static void
xfs_buf_destroy(
struct xfs_buf *bp)
{
- ASSERT(__lockref_is_dead(&bp->b_lockref));
+ ASSERT(lockref_is_dead(&bp->b_lockref));
ASSERT(!(bp->b_flags & _XBF_DELWRI_Q));
if (bp->b_pag)
diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c
index 896b24f87ac9..99a82107b8e6 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -128,7 +128,7 @@ xfs_qm_dqpurge(
struct xfs_quotainfo *qi = dqp->q_mount->m_quotainfo;
spin_lock(&dqp->q_lockref.lock);
- if (dqp->q_lockref.count > 0 || __lockref_is_dead(&dqp->q_lockref)) {
+ if (dqp->q_lockref.count > 0 || lockref_is_dead(&dqp->q_lockref)) {
spin_unlock(&dqp->q_lockref.lock);
return -EAGAIN;
}
@@ -429,7 +429,7 @@ xfs_qm_dquot_isolate(
* from the LRU, leave it for the freeing task to complete the freeing
* process rather than risk it being free from under us here.
*/
- if (__lockref_is_dead(&dqp->q_lockref))
+ if (lockref_is_dead(&dqp->q_lockref))
goto out_miss_unlock;
/*