summaryrefslogtreecommitdiff
path: root/fs/ocfs2
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ocfs2')
-rw-r--r--fs/ocfs2/acl.c135
-rw-r--r--fs/ocfs2/acl.h19
-rw-r--r--fs/ocfs2/alloc.c2
-rw-r--r--fs/ocfs2/aops.c13
-rw-r--r--fs/ocfs2/buffer_head_io.c23
-rw-r--r--fs/ocfs2/cluster/heartbeat.c381
-rw-r--r--fs/ocfs2/cluster/heartbeat.h5
-rw-r--r--fs/ocfs2/cluster/nodemanager.c29
-rw-r--r--fs/ocfs2/cluster/nodemanager.h3
-rw-r--r--fs/ocfs2/cluster/tcp.c186
-rw-r--r--fs/ocfs2/cluster/tcp.h2
-rw-r--r--fs/ocfs2/dir.c98
-rw-r--r--fs/ocfs2/dlm/dlmdebug.c40
-rw-r--r--fs/ocfs2/dlm/dlmdomain.c8
-rw-r--r--fs/ocfs2/dlm/dlmmaster.c11
-rw-r--r--fs/ocfs2/dlm/dlmrecovery.c13
-rw-r--r--fs/ocfs2/dlmfs/dlmfs.c5
-rw-r--r--fs/ocfs2/dlmglue.c17
-rw-r--r--fs/ocfs2/file.c23
-rw-r--r--fs/ocfs2/inode.c228
-rw-r--r--fs/ocfs2/journal.c13
-rw-r--r--fs/ocfs2/journal.h3
-rw-r--r--fs/ocfs2/move_extents.c4
-rw-r--r--fs/ocfs2/namei.c42
-rw-r--r--fs/ocfs2/ocfs2.h2
-rw-r--r--fs/ocfs2/quota_local.c2
-rw-r--r--fs/ocfs2/refcounttree.c56
-rw-r--r--fs/ocfs2/stack_user.c10
-rw-r--r--fs/ocfs2/suballoc.c24
-rw-r--r--fs/ocfs2/super.c4
-rw-r--r--fs/ocfs2/sysfile.c9
-rw-r--r--fs/ocfs2/xattr.c428
-rw-r--r--fs/ocfs2/xattr.h11
33 files changed, 1416 insertions, 433 deletions
diff --git a/fs/ocfs2/acl.c b/fs/ocfs2/acl.c
index af1e2cedb217..090ec60fb576 100644
--- a/fs/ocfs2/acl.c
+++ b/fs/ocfs2/acl.c
@@ -110,8 +110,7 @@ static void *ocfs2_acl_to_xattr(const struct posix_acl *acl, size_t *size)
return ocfs2_acl;
}
-static struct posix_acl *ocfs2_get_acl_nolock(struct inode *inode,
- int type,
+static struct posix_acl *ocfs2_get_acl_nolock(struct inode *inode, int type,
struct buffer_head *di_bh)
{
int name_index;
@@ -349,63 +348,105 @@ int ocfs2_acl_chmod(struct inode *inode, struct buffer_head *bh)
* Initialize the ACLs of a new inode. If parent directory has default ACL,
* then clone to new inode. Called from ocfs2_mknod.
*/
-int ocfs2_init_acl(handle_t *handle,
- struct inode *inode,
- struct inode *dir,
- struct buffer_head *di_bh,
- struct buffer_head *dir_bh,
- struct ocfs2_alloc_context *meta_ac,
- struct ocfs2_alloc_context *data_ac)
+void ocfs2_acl_init_release(struct ocfs2_acl_state *state)
+{
+ posix_acl_release(state->default_acl);
+ posix_acl_release(state->acl);
+ state->default_acl = NULL;
+ state->acl = NULL;
+}
+
+int ocfs2_acl_init_prepare(struct inode *inode, struct inode *dir,
+ struct buffer_head *dir_bh,
+ struct ocfs2_acl_state *state)
{
struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
- struct posix_acl *acl = NULL;
- int ret = 0, ret2;
- umode_t mode;
-
- if (!S_ISLNK(inode->i_mode)) {
- if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
- down_read(&OCFS2_I(dir)->ip_xattr_sem);
- acl = ocfs2_get_acl_nolock(dir, ACL_TYPE_DEFAULT,
- dir_bh);
- up_read(&OCFS2_I(dir)->ip_xattr_sem);
- if (IS_ERR(acl))
- return PTR_ERR(acl);
+ int ret = 0;
+
+ state->default_acl = NULL;
+ state->acl = NULL;
+ state->mode = inode->i_mode;
+
+ if (S_ISLNK(inode->i_mode))
+ return 0;
+
+ if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
+ down_read(&OCFS2_I(dir)->ip_xattr_sem);
+ state->default_acl =
+ ocfs2_get_acl_nolock(dir, ACL_TYPE_DEFAULT, dir_bh);
+ up_read(&OCFS2_I(dir)->ip_xattr_sem);
+ if (IS_ERR(state->default_acl)) {
+ ret = PTR_ERR(state->default_acl);
+ state->default_acl = NULL;
+ return ret;
}
- if (!acl) {
- mode = inode->i_mode & ~current_umask();
- ret = ocfs2_acl_set_mode(inode, di_bh, handle, mode);
- if (ret) {
- mlog_errno(ret);
+ if (state->default_acl) {
+ state->acl = posix_acl_dup(state->default_acl);
+ if (!state->acl) {
+ ret = -ENOMEM;
goto cleanup;
}
+ ret = __posix_acl_create(&state->acl, GFP_NOFS,
+ &state->mode);
+ if (ret < 0)
+ goto cleanup;
+ if (ret == 0) {
+ posix_acl_release(state->acl);
+ state->acl = NULL;
+ }
+ if (!S_ISDIR(inode->i_mode)) {
+ posix_acl_release(state->default_acl);
+ state->default_acl = NULL;
+ }
+ } else {
+ state->mode &= ~current_umask();
}
+ } else {
+ state->mode &= ~current_umask();
}
- if ((osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) && acl) {
- if (S_ISDIR(inode->i_mode)) {
+
+ return 0;
+cleanup:
+ ocfs2_acl_init_release(state);
+ return ret;
+}
+
+int ocfs2_init_acl(handle_t *handle, struct inode *inode,
+ struct buffer_head *di_bh,
+ struct ocfs2_alloc_context *meta_ac,
+ struct ocfs2_alloc_context *data_ac,
+ struct ocfs2_acl_state *state)
+{
+ struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+ int ret = 0;
+
+ if (S_ISLNK(inode->i_mode))
+ return 0;
+
+ if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
+ if (S_ISDIR(inode->i_mode) && state->default_acl) {
ret = ocfs2_set_acl(handle, inode, di_bh,
- ACL_TYPE_DEFAULT, acl,
- meta_ac, data_ac);
+ ACL_TYPE_DEFAULT,
+ state->default_acl, meta_ac,
+ data_ac);
if (ret)
- goto cleanup;
+ return ret;
}
- mode = inode->i_mode;
- ret = __posix_acl_create(&acl, GFP_NOFS, &mode);
- if (ret < 0)
- return ret;
+ }
- ret2 = ocfs2_acl_set_mode(inode, di_bh, handle, mode);
- if (ret2) {
- mlog_errno(ret2);
- ret = ret2;
- goto cleanup;
- }
- if (ret > 0) {
- ret = ocfs2_set_acl(handle, inode,
- di_bh, ACL_TYPE_ACCESS,
- acl, meta_ac, data_ac);
+ ret = ocfs2_acl_set_mode(inode, di_bh, handle, state->mode);
+ if (ret) {
+ mlog_errno(ret);
+ return ret;
+ }
+
+ if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
+ if (state->acl) {
+ ret = ocfs2_set_acl(handle, inode, di_bh,
+ ACL_TYPE_ACCESS, state->acl,
+ meta_ac, data_ac);
}
}
-cleanup:
- posix_acl_release(acl);
+
return ret;
}
diff --git a/fs/ocfs2/acl.h b/fs/ocfs2/acl.h
index 667c6f03fa60..a91f9ce278d6 100644
--- a/fs/ocfs2/acl.h
+++ b/fs/ocfs2/acl.h
@@ -20,9 +20,20 @@ struct posix_acl *ocfs2_iop_get_acl(struct inode *inode, int type, bool rcu);
int ocfs2_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
struct posix_acl *acl, int type);
extern int ocfs2_acl_chmod(struct inode *, struct buffer_head *);
-extern int ocfs2_init_acl(handle_t *, struct inode *, struct inode *,
- struct buffer_head *, struct buffer_head *,
- struct ocfs2_alloc_context *,
- struct ocfs2_alloc_context *);
+struct ocfs2_acl_state {
+ struct posix_acl *default_acl;
+ struct posix_acl *acl;
+ umode_t mode;
+};
+
+int ocfs2_acl_init_prepare(struct inode *inode, struct inode *dir,
+ struct buffer_head *dir_bh,
+ struct ocfs2_acl_state *state);
+void ocfs2_acl_init_release(struct ocfs2_acl_state *state);
+int ocfs2_init_acl(handle_t *handle, struct inode *inode,
+ struct buffer_head *di_bh,
+ struct ocfs2_alloc_context *meta_ac,
+ struct ocfs2_alloc_context *data_ac,
+ struct ocfs2_acl_state *state);
#endif /* OCFS2_ACL_H */
diff --git a/fs/ocfs2/alloc.c b/fs/ocfs2/alloc.c
index 6e5fd3f12a84..be09e766ac1f 100644
--- a/fs/ocfs2/alloc.c
+++ b/fs/ocfs2/alloc.c
@@ -7576,7 +7576,7 @@ int ocfs2_trim_mainbm(struct super_block *sb, struct fstrim_range *range)
len = range->len >> osb->s_clustersize_bits;
minlen = range->minlen >> osb->s_clustersize_bits;
- if (minlen >= osb->bitmap_cpg || range->len < sb->s_blocksize)
+ if (minlen >= osb->bitmap_cpg || range->len < osb->s_clustersize)
return -EINVAL;
trace_ocfs2_trim_mainbm(start, len, minlen);
diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
index 6ec198bdab12..4acdbb70882c 100644
--- a/fs/ocfs2/aops.c
+++ b/fs/ocfs2/aops.c
@@ -2372,6 +2372,15 @@ commit:
unlock:
up_write(&oi->ip_alloc_sem);
+ if (data_ac) {
+ ocfs2_free_alloc_context(data_ac);
+ data_ac = NULL;
+ }
+ if (meta_ac) {
+ ocfs2_free_alloc_context(meta_ac);
+ meta_ac = NULL;
+ }
+
/* everything looks good, let's start the cleanup */
if (!ret && dwc->dw_orphaned) {
BUG_ON(dwc->dw_writer_pid != task_pid_nr(current));
@@ -2383,10 +2392,6 @@ unlock:
ocfs2_inode_unlock(inode, 1);
brelse(di_bh);
out:
- if (data_ac)
- ocfs2_free_alloc_context(data_ac);
- if (meta_ac)
- ocfs2_free_alloc_context(meta_ac);
ocfs2_run_deallocs(osb, &dealloc);
ocfs2_dio_free_write_ctx(inode, dwc);
diff --git a/fs/ocfs2/buffer_head_io.c b/fs/ocfs2/buffer_head_io.c
index 701d27d908d4..7bfe377af2df 100644
--- a/fs/ocfs2/buffer_head_io.c
+++ b/fs/ocfs2/buffer_head_io.c
@@ -62,9 +62,7 @@ int ocfs2_write_block(struct ocfs2_super *osb, struct buffer_head *bh,
/* remove from dirty list before I/O. */
clear_buffer_dirty(bh);
- get_bh(bh); /* for end_buffer_write_sync() */
- bh->b_end_io = end_buffer_write_sync;
- submit_bh(REQ_OP_WRITE, bh);
+ bh_submit(bh, REQ_OP_WRITE, bh_end_write);
wait_on_buffer(bh);
@@ -145,9 +143,7 @@ int ocfs2_read_blocks_sync(struct ocfs2_super *osb, u64 block,
#endif
}
- get_bh(bh); /* for end_buffer_read_sync() */
- bh->b_end_io = end_buffer_read_sync;
- submit_bh(REQ_OP_READ, bh);
+ bh_submit(bh, REQ_OP_READ, bh_end_read);
}
read_failure:
@@ -323,11 +319,9 @@ int ocfs2_read_blocks(struct ocfs2_caching_info *ci, u64 block, int nr,
continue;
}
- get_bh(bh); /* for end_buffer_read_sync() */
if (validate)
set_buffer_needs_validate(bh);
- bh->b_end_io = end_buffer_read_sync;
- submit_bh(REQ_OP_READ, bh);
+ bh_submit(bh, REQ_OP_READ, bh_end_read);
continue;
}
}
@@ -350,8 +344,6 @@ read_failure:
wait_on_buffer(bh);
put_bh(bh);
bhs[i] = NULL;
- } else if (bh && buffer_uptodate(bh)) {
- clear_buffer_uptodate(bh);
}
continue;
}
@@ -380,8 +372,11 @@ read_failure:
BUG_ON(buffer_jbd(bh));
clear_buffer_needs_validate(bh);
status = validate(sb, bh);
- if (status)
+ if (status) {
+ if (buffer_uptodate(bh))
+ clear_buffer_uptodate(bh);
goto read_failure;
+ }
}
}
@@ -446,10 +441,8 @@ int ocfs2_write_super_or_backup(struct ocfs2_super *osb,
/* remove from dirty list before I/O. */
clear_buffer_dirty(bh);
- get_bh(bh); /* for end_buffer_write_sync() */
- bh->b_end_io = end_buffer_write_sync;
ocfs2_compute_meta_ecc(osb->sb, bh->b_data, &di->i_check);
- submit_bh(REQ_OP_WRITE, bh);
+ bh_submit(bh, REQ_OP_WRITE, bh_end_write);
wait_on_buffer(bh);
diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c
index d12784aaaa4b..1c3def99bb07 100644
--- a/fs/ocfs2/cluster/heartbeat.c
+++ b/fs/ocfs2/cluster/heartbeat.c
@@ -15,6 +15,7 @@
#include <linux/file.h>
#include <linux/kthread.h>
#include <linux/configfs.h>
+#include <linux/mutex.h>
#include <linux/random.h>
#include <linux/crc32.h>
#include <linux/time.h>
@@ -43,6 +44,14 @@ static DECLARE_RWSEM(o2hb_callback_sem);
* whenever any of the threads sees activity from the node in its region.
*/
static DEFINE_SPINLOCK(o2hb_live_lock);
+/*
+ * Serializes region pin/unpin dependency management (o2hb_dependent_users
+ * and the o2nm_depend_item()/o2nm_undepend_item() calls). o2hb_region_pin()
+ * has to drop o2hb_live_lock across the sleeping o2nm_depend_item(), so the
+ * spinlock alone can no longer keep pin and unpin mutually exclusive; this
+ * mutex, taken outside o2hb_live_lock, does.
+ */
+static DEFINE_MUTEX(o2hb_dependency_mutex);
static struct list_head o2hb_live_slots[O2NM_MAX_NODES];
static unsigned long o2hb_live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)];
static LIST_HEAD(o2hb_node_events);
@@ -138,7 +147,7 @@ static unsigned int o2hb_dependent_users;
* In global heartbeat mode, we pin/unpin all o2hb regions. This solution
* works for both file system and userdlm domains.
*/
-static int o2hb_region_pin(const char *region_uuid);
+static int o2hb_region_pin(const char *region_uuid, bool from_callback);
static void o2hb_region_unpin(const char *region_uuid);
/* Only sets a new threshold if there are no active regions.
@@ -203,6 +212,7 @@ struct o2hb_region {
/* protected by the hr_callback_sem */
struct task_struct *hr_task;
+ u8 hr_node_num;
unsigned int hr_blocks;
unsigned long long hr_start_block;
@@ -257,6 +267,9 @@ struct o2hb_region {
/* Message key for negotiate timeout message. */
unsigned int hr_key;
struct list_head hr_handler_list;
+ /* Serializes timeout arming against failed-start and teardown. */
+ struct mutex hr_arming_mutex;
+ bool hr_stopping;
/* last hb status, 0 for success, other value for error. */
int hr_last_hb_status;
@@ -271,6 +284,9 @@ struct o2hb_bio_wait_ctxt {
atomic_t wc_num_reqs;
struct completion wc_io_complete;
int wc_error;
+ /* On-stack bio used by the synchronous write path only. */
+ struct bio wc_write_bio;
+ struct bio_vec wc_write_bvec;
};
#define O2HB_NEGO_TIMEOUT_MS (O2HB_MAX_WRITE_TIMEOUT_MS/2)
@@ -321,9 +337,14 @@ static void o2hb_write_timeout(struct work_struct *work)
static void o2hb_arm_timeout(struct o2hb_region *reg)
{
+ mutex_lock(&reg->hr_arming_mutex);
+
+ if (reg->hr_stopping)
+ goto out_unlock;
+
/* Arm writeout only after thread reaches steady state */
if (atomic_read(&reg->hr_steady_iterations) != 0)
- return;
+ goto out_unlock;
mlog(ML_HEARTBEAT, "Queue write timeout for %u ms\n",
O2HB_MAX_WRITE_TIMEOUT_MS);
@@ -342,6 +363,18 @@ static void o2hb_arm_timeout(struct o2hb_region *reg)
schedule_delayed_work(&reg->hr_nego_timeout_work,
msecs_to_jiffies(O2HB_NEGO_TIMEOUT_MS));
bitmap_zero(reg->hr_nego_node_bitmap, O2NM_MAX_NODES);
+
+out_unlock:
+ mutex_unlock(&reg->hr_arming_mutex);
+}
+
+static void o2hb_queue_nego_timeout(struct o2hb_region *reg,
+ unsigned long delay)
+{
+ mutex_lock(&reg->hr_arming_mutex);
+ if (!reg->hr_stopping)
+ schedule_delayed_work(&reg->hr_nego_timeout_work, delay);
+ mutex_unlock(&reg->hr_arming_mutex);
}
static void o2hb_disarm_timeout(struct o2hb_region *reg)
@@ -350,12 +383,25 @@ static void o2hb_disarm_timeout(struct o2hb_region *reg)
cancel_delayed_work_sync(&reg->hr_nego_timeout_work);
}
-static int o2hb_send_nego_msg(int key, int type, u8 target)
+static void o2hb_set_region_stopping(struct o2hb_region *reg, bool stopping)
+{
+ mutex_lock(&reg->hr_arming_mutex);
+ reg->hr_stopping = stopping;
+ mutex_unlock(&reg->hr_arming_mutex);
+}
+
+static void o2hb_quiesce_timeout(struct o2hb_region *reg)
+{
+ o2hb_set_region_stopping(reg, true);
+ o2hb_disarm_timeout(reg);
+}
+
+static int o2hb_send_nego_msg(int key, int type, u8 target, u8 node_num)
{
struct o2hb_nego_msg msg;
int status, ret;
- msg.node_num = o2nm_this_node();
+ msg.node_num = node_num;
again:
ret = o2net_send_message(type, key, &msg, sizeof(msg),
target, &status);
@@ -373,8 +419,10 @@ static void o2hb_nego_timeout(struct work_struct *work)
unsigned long live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)];
int master_node, i, ret;
struct o2hb_region *reg;
+ u8 node_num;
reg = container_of(work, struct o2hb_region, hr_nego_timeout_work.work);
+ node_num = reg->hr_node_num;
/* don't negotiate timeout if last hb failed since it is very
* possible io failed. Should let write timeout fence self.
*/
@@ -385,10 +433,10 @@ static void o2hb_nego_timeout(struct work_struct *work)
/* lowest node as master node to make negotiate decision. */
master_node = find_first_bit(live_node_bitmap, O2NM_MAX_NODES);
- if (master_node == o2nm_this_node()) {
+ if (master_node == node_num) {
if (!test_bit(master_node, reg->hr_nego_node_bitmap)) {
printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg).\n",
- o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000,
+ node_num, O2HB_NEGO_TIMEOUT_MS / 1000,
config_item_name(&reg->hr_item), reg_bdev(reg));
set_bit(master_node, reg->hr_nego_node_bitmap);
}
@@ -397,8 +445,7 @@ static void o2hb_nego_timeout(struct work_struct *work)
/* check negotiate bitmap every second to do timeout
* approve decision.
*/
- schedule_delayed_work(&reg->hr_nego_timeout_work,
- msecs_to_jiffies(1000));
+ o2hb_queue_nego_timeout(reg, msecs_to_jiffies(1000));
return;
}
@@ -417,7 +464,7 @@ static void o2hb_nego_timeout(struct work_struct *work)
mlog(ML_HEARTBEAT, "send NEGO_APPROVE msg to node %d\n", i);
ret = o2hb_send_nego_msg(reg->hr_key,
- O2HB_NEGO_APPROVE_MSG, i);
+ O2HB_NEGO_APPROVE_MSG, i, node_num);
if (ret)
mlog(ML_ERROR, "send NEGO_APPROVE msg to node %d fail %d\n",
i, ret);
@@ -425,10 +472,10 @@ static void o2hb_nego_timeout(struct work_struct *work)
} else {
/* negotiate timeout with master node. */
printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg), negotiate timeout with node %d.\n",
- o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000, config_item_name(&reg->hr_item),
+ node_num, O2HB_NEGO_TIMEOUT_MS / 1000, config_item_name(&reg->hr_item),
reg_bdev(reg), master_node);
ret = o2hb_send_nego_msg(reg->hr_key, O2HB_NEGO_TIMEOUT_MSG,
- master_node);
+ master_node, node_num);
if (ret)
mlog(ML_ERROR, "send NEGO_TIMEOUT msg to node %d fail %d\n",
master_node, ret);
@@ -504,6 +551,23 @@ static void o2hb_bio_end_io(struct bio *bio)
bio_put(bio);
}
+/*
+ * End I/O for the synchronous write path. The write bio is embedded in
+ * the wait ctxt rather than allocated, so it must not be freed here; it
+ * is torn down with bio_uninit() once the caller has waited on it.
+ */
+static void o2hb_write_bio_end_io(struct bio *bio)
+{
+ struct o2hb_bio_wait_ctxt *wc = bio->bi_private;
+
+ if (bio->bi_status) {
+ mlog(ML_ERROR, "IO Error %d\n", bio->bi_status);
+ wc->wc_error = blk_status_to_errno(bio->bi_status);
+ }
+
+ o2hb_bio_wait_dec(wc, 1);
+}
+
/* Setup a Bio to cover I/O against num_slots slots starting at
* start_slot. */
static struct bio *o2hb_setup_one_bio(struct o2hb_region *reg,
@@ -519,16 +583,12 @@ static struct bio *o2hb_setup_one_bio(struct o2hb_region *reg,
struct bio *bio;
struct page *page;
- /* Testing has shown this allocation to take long enough under
- * GFP_KERNEL that the local node can get fenced. It would be
- * nicest if we could pre-allocate these bios and avoid this
- * all together. */
- bio = bio_alloc(reg_bdev(reg), 16, opf, GFP_ATOMIC);
- if (!bio) {
- mlog(ML_ERROR, "Could not alloc slots BIO!\n");
- bio = ERR_PTR(-ENOMEM);
- goto bail;
- }
+ /*
+ * The heartbeat runs in process context and can sleep, so use
+ * GFP_NOFS. It is backed by the fs_bio_set mempool and thus cannot
+ * fail, while avoiding recursion back into the filesystem.
+ */
+ bio = bio_alloc(reg_bdev(reg), 16, opf, GFP_NOFS);
/* Must put everything in 512 byte sectors for the bio... */
bio->bi_iter.bi_sector = (reg->hr_start_block + cs) << (bits - 9);
@@ -553,7 +613,6 @@ static struct bio *o2hb_setup_one_bio(struct o2hb_region *reg,
vec_start = 0;
}
-bail:
*current_slot = cs;
return bio;
}
@@ -563,7 +622,6 @@ static int o2hb_read_slots(struct o2hb_region *reg,
unsigned int max_slots)
{
unsigned int current_slot = begin_slot;
- int status;
struct o2hb_bio_wait_ctxt wc;
struct bio *bio;
@@ -572,51 +630,51 @@ static int o2hb_read_slots(struct o2hb_region *reg,
while(current_slot < max_slots) {
bio = o2hb_setup_one_bio(reg, &wc, &current_slot, max_slots,
REQ_OP_READ);
- if (IS_ERR(bio)) {
- status = PTR_ERR(bio);
- mlog_errno(status);
- goto bail_and_wait;
- }
-
atomic_inc(&wc.wc_num_reqs);
submit_bio(bio);
}
- status = 0;
-
-bail_and_wait:
o2hb_wait_on_io(&wc);
- if (wc.wc_error && !status)
- status = wc.wc_error;
- return status;
+ return wc.wc_error;
}
static int o2hb_issue_node_write(struct o2hb_region *reg,
struct o2hb_bio_wait_ctxt *write_wc)
{
- int status;
unsigned int slot;
- struct bio *bio;
+ unsigned int bits = reg->hr_block_bits;
+ unsigned int spp = reg->hr_slots_per_page;
+ unsigned int vec_start, vec_len;
+ struct page *page;
+ struct bio *bio = &write_wc->wc_write_bio;
o2hb_bio_wait_init(write_wc);
- slot = o2nm_this_node();
+ slot = reg->hr_node_num;
+ if (slot >= O2NM_MAX_NODES)
+ return -EINVAL;
- bio = o2hb_setup_one_bio(reg, write_wc, &slot, slot+1,
- REQ_OP_WRITE | REQ_SYNC);
- if (IS_ERR(bio)) {
- status = PTR_ERR(bio);
- mlog_errno(status);
- goto bail;
- }
+ /*
+ * The heartbeat write always covers our own single slot, i.e. one
+ * block that lives within a single page. Use an on-stack bio (embedded
+ * in write_wc) so this fence-critical path never has to allocate.
+ */
+ bio_init(bio, reg_bdev(reg), &write_wc->wc_write_bvec, 1,
+ REQ_OP_WRITE | REQ_SYNC);
+ bio->bi_iter.bi_sector = (reg->hr_start_block + slot) << (bits - 9);
+ bio->bi_private = write_wc;
+ bio->bi_end_io = o2hb_write_bio_end_io;
+
+ page = reg->hr_slot_data[slot / spp];
+ vec_start = (slot << bits) % PAGE_SIZE;
+ vec_len = PAGE_SIZE / spp;
+ __bio_add_page(bio, page, vec_len, vec_start);
atomic_inc(&write_wc->wc_num_reqs);
submit_bio(bio);
- status = 0;
-bail:
- return status;
+ return 0;
}
static u32 o2hb_compute_block_crc_le(struct o2hb_region *reg,
@@ -670,8 +728,12 @@ static int o2hb_check_own_slot(struct o2hb_region *reg)
struct o2hb_disk_slot *slot;
struct o2hb_disk_heartbeat_block *hb_block;
char *errstr;
+ u8 node_num = reg->hr_node_num;
- slot = &reg->hr_slots[o2nm_this_node()];
+ if (node_num >= O2NM_MAX_NODES)
+ return 0;
+
+ slot = &reg->hr_slots[node_num];
/* Don't check on our 1st timestamp */
if (!slot->ds_last_time)
return 0;
@@ -712,7 +774,10 @@ static inline void o2hb_prepare_block(struct o2hb_region *reg,
struct o2hb_disk_slot *slot;
struct o2hb_disk_heartbeat_block *hb_block;
- node_num = o2nm_this_node();
+ node_num = reg->hr_node_num;
+ if (node_num >= O2NM_MAX_NODES)
+ return;
+
slot = &reg->hr_slots[node_num];
hb_block = (struct o2hb_disk_heartbeat_block *)slot->ds_raw_block;
@@ -1146,6 +1211,7 @@ static int o2hb_do_disk_heartbeat(struct o2hb_region *reg)
* people we find in our steady state have seen us.
*/
o2hb_wait_on_io(&write_wc);
+ bio_uninit(&write_wc.wc_write_bio);
if (write_wc.wc_error) {
/* Do not re-arm the write timeout on I/O error - we
* can't be sure that the new block ever made it to
@@ -1206,7 +1272,7 @@ static int o2hb_thread(void *data)
set_user_nice(current, MIN_NICE);
/* Pin node */
- ret = o2nm_depend_this_node();
+ ret = o2nm_depend_node(reg->hr_node_num);
if (ret) {
mlog(ML_ERROR, "Node has been deleted, ret = %d\n", ret);
reg->hr_node_deleted = 1;
@@ -1215,7 +1281,8 @@ static int o2hb_thread(void *data)
}
while (!kthread_should_stop() &&
- !reg->hr_unclean_stop && !reg->hr_aborted_start) {
+ !reg->hr_unclean_stop && !reg->hr_aborted_start &&
+ o2nm_this_node() == reg->hr_node_num) {
/* We track the time spent inside
* o2hb_do_disk_heartbeat so that we avoid more than
* hr_timeout_ms between disk writes. On busy systems
@@ -1257,14 +1324,16 @@ static int o2hb_thread(void *data)
if (!reg->hr_unclean_stop && !reg->hr_aborted_start) {
o2hb_prepare_block(reg, 0);
ret = o2hb_issue_node_write(reg, &write_wc);
- if (ret == 0)
+ if (ret == 0) {
o2hb_wait_on_io(&write_wc);
- else
+ bio_uninit(&write_wc.wc_write_bio);
+ } else {
mlog_errno(ret);
+ }
}
/* Unpin node */
- o2nm_undepend_this_node();
+ o2nm_undepend_node(reg->hr_node_num);
mlog(ML_HEARTBEAT|ML_KTHREAD, "o2hb thread exiting\n");
@@ -1456,13 +1525,38 @@ void o2hb_init(void)
o2hb_debug_init();
}
-/* if we're already in a callback then we're already serialized by the sem */
-static void o2hb_fill_node_map_from_callback(unsigned long *map,
- unsigned int bits)
+static void __o2hb_fill_node_map(unsigned long *map, unsigned int bits)
{
bitmap_copy(map, o2hb_live_node_bitmap, bits);
}
+void o2hb_callback_read_lock(void)
+{
+ down_read(&o2hb_callback_sem);
+}
+
+void o2hb_callback_read_unlock(void)
+{
+ up_read(&o2hb_callback_sem);
+}
+
+void o2hb_synchronize_callbacks(void)
+{
+ down_write(&o2hb_callback_sem);
+ up_write(&o2hb_callback_sem);
+}
+
+/*
+ * Callers must already hold o2hb_callback_sem for read or write so the copy
+ * stays serialized with callback delivery.
+ */
+void o2hb_fill_node_map_locked(unsigned long *map, unsigned int bits)
+{
+ spin_lock(&o2hb_live_lock);
+ __o2hb_fill_node_map(map, bits);
+ spin_unlock(&o2hb_live_lock);
+}
+
/*
* get a map of all nodes that are heartbeating in any regions
*/
@@ -1470,11 +1564,9 @@ void o2hb_fill_node_map(unsigned long *map, unsigned int bits)
{
/* callers want to serialize this map and callbacks so that they
* can trust that they don't miss nodes coming to the party */
- down_read(&o2hb_callback_sem);
- spin_lock(&o2hb_live_lock);
- o2hb_fill_node_map_from_callback(map, bits);
- spin_unlock(&o2hb_live_lock);
- up_read(&o2hb_callback_sem);
+ o2hb_callback_read_lock();
+ o2hb_fill_node_map_locked(map, bits);
+ o2hb_callback_read_unlock();
}
EXPORT_SYMBOL_GPL(o2hb_fill_node_map);
@@ -1522,6 +1614,8 @@ static void o2hb_region_release(struct config_item *item)
mlog(ML_HEARTBEAT, "hb region release (%pg)\n", reg_bdev(reg));
+ o2hb_quiesce_timeout(reg);
+ o2net_unregister_and_flush_handler_list(&reg->hr_handler_list);
o2hb_unmap_slot_data(reg);
if (reg->hr_bdev_file)
@@ -1537,7 +1631,6 @@ static void o2hb_region_release(struct config_item *item)
list_del(&reg->hr_all_item);
spin_unlock(&o2hb_live_lock);
- o2net_unregister_handler_list(&reg->hr_handler_list);
kfree(reg);
}
@@ -1791,7 +1884,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
/* We can't heartbeat without having had our node number
* configured yet. */
- if (o2nm_this_node() == O2NM_MAX_NODES)
+ reg->hr_node_num = o2nm_this_node();
+ if (reg->hr_node_num == O2NM_MAX_NODES)
return -EINVAL;
ret = kstrtol(p, 0, &fd);
@@ -1851,9 +1945,6 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
goto out;
}
- INIT_DELAYED_WORK(&reg->hr_write_timeout_work, o2hb_write_timeout);
- INIT_DELAYED_WORK(&reg->hr_nego_timeout_work, o2hb_nego_timeout);
-
/*
* A node is considered live after it has beat LIVE_THRESHOLD
* times. We're not steady until we've given them a chance
@@ -1873,6 +1964,7 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
atomic_set(&reg->hr_steady_iterations, live_threshold);
/* unsteady_iterations is triple the steady_iterations */
atomic_set(&reg->hr_unsteady_iterations, (live_threshold * 3));
+ o2hb_set_region_stopping(reg, false);
hb_task = kthread_run(o2hb_thread, reg, "o2hb-%s",
reg->hr_item.ci_name);
@@ -1922,6 +2014,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
out:
if (ret < 0) {
+ o2hb_quiesce_timeout(reg);
+
spin_lock(&o2hb_live_lock);
hb_task = reg->hr_task;
reg->hr_task = NULL;
@@ -2036,6 +2130,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
ret = -ENAMETOOLONG;
goto free;
}
+ reg->hr_node_num = O2NM_MAX_NODES;
spin_lock(&o2hb_live_lock);
reg->hr_region_num = 0;
@@ -2060,6 +2155,10 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
*/
reg->hr_key = crc32_le(reg->hr_region_num + O2NM_MAX_REGIONS,
name, strlen(name));
+ mutex_init(&reg->hr_arming_mutex);
+ reg->hr_stopping = true;
+ INIT_DELAYED_WORK(&reg->hr_write_timeout_work, o2hb_write_timeout);
+ INIT_DELAYED_WORK(&reg->hr_nego_timeout_work, o2hb_nego_timeout);
INIT_LIST_HEAD(&reg->hr_handler_list);
ret = o2net_register_handler(O2HB_NEGO_TIMEOUT_MSG, reg->hr_key,
sizeof(struct o2hb_nego_msg),
@@ -2080,7 +2179,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
return &reg->hr_item;
unregister_handler:
- o2net_unregister_handler_list(&reg->hr_handler_list);
+ o2net_unregister_and_flush_handler_list(&reg->hr_handler_list);
remove_item:
spin_lock(&o2hb_live_lock);
list_del(&reg->hr_all_item);
@@ -2099,6 +2198,8 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group,
struct o2hb_region *reg = to_o2hb_region(item);
int quorum_region = 0;
+ o2hb_quiesce_timeout(reg);
+
/* stop the thread when the user removes the region dir */
spin_lock(&o2hb_live_lock);
hb_task = reg->hr_task;
@@ -2142,6 +2243,7 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group,
* If global heartbeat active and there are dependent users,
* pin all regions if quorum region count <= CUT_OFF
*/
+ mutex_lock(&o2hb_dependency_mutex);
spin_lock(&o2hb_live_lock);
if (!o2hb_dependent_users)
@@ -2149,10 +2251,11 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group,
if (bitmap_weight(o2hb_quorum_region_bitmap,
O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF)
- o2hb_region_pin(NULL);
+ o2hb_region_pin(NULL, true);
unlock:
spin_unlock(&o2hb_live_lock);
+ mutex_unlock(&o2hb_dependency_mutex);
}
static ssize_t o2hb_heartbeat_group_dead_threshold_show(struct config_item *item,
@@ -2290,48 +2393,113 @@ EXPORT_SYMBOL_GPL(o2hb_setup_callback);
* In local, we only pin the matching region. In global we pin all the active
* regions.
*/
-static int o2hb_region_pin(const char *region_uuid)
+static int o2hb_region_pin(const char *region_uuid, bool from_callback)
{
- int ret = 0, found = 0;
- struct o2hb_region *reg;
+ int ret = 0, found;
+ struct o2hb_region *reg, *pinned;
char *uuid;
assert_spin_locked(&o2hb_live_lock);
- list_for_each_entry(reg, &o2hb_all_regions, hr_all_item) {
- if (reg->hr_item_dropped)
- continue;
+ do {
+ found = 0;
+ pinned = NULL;
- uuid = config_item_name(&reg->hr_item);
+ list_for_each_entry(reg, &o2hb_all_regions, hr_all_item) {
+ if (reg->hr_item_dropped)
+ continue;
- /* local heartbeat */
- if (region_uuid) {
- if (strcmp(region_uuid, uuid))
+ uuid = config_item_name(&reg->hr_item);
+
+ /* local heartbeat */
+ if (region_uuid) {
+ if (strcmp(region_uuid, uuid))
+ continue;
+ found = 1;
+ }
+
+ if (reg->hr_item_pinned || reg->hr_item_dropped) {
+ if (found)
+ break;
continue;
- found = 1;
+ }
+
+ /*
+ * Found a region that needs pinning. Take a reference
+ * so it stays alive while we drop the lock below.
+ */
+ pinned = reg;
+ config_item_get(&reg->hr_item);
+ break;
}
- if (reg->hr_item_pinned || reg->hr_item_dropped)
- goto skip_pin;
+ if (!pinned)
+ break;
+
+ uuid = config_item_name(&pinned->hr_item);
+
+ /*
+ * o2nm_depend_item() -> configfs_depend_item() can sleep (it
+ * takes the configfs root inode rwsem), so it must not run
+ * under o2hb_live_lock. Drop the lock across it; @pinned is
+ * kept alive by the reference taken above. The region list may
+ * change while unlocked, so we rescan from the top afterwards.
+ */
+ spin_unlock(&o2hb_live_lock);
/* Ignore ENOENT only for local hb (userdlm domain) */
- ret = o2nm_depend_item(&reg->hr_item);
+ if (from_callback)
+ ret = o2nm_depend_item_unlocked(&pinned->hr_item);
+ else
+ ret = o2nm_depend_item(&pinned->hr_item);
+
+ spin_lock(&o2hb_live_lock);
if (!ret) {
- mlog(ML_CLUSTER, "Pin region %s\n", uuid);
- reg->hr_item_pinned = 1;
- } else {
- if (ret == -ENOENT && found)
- ret = 0;
- else {
- mlog(ML_ERROR, "Pin region %s fails with %d\n",
- uuid, ret);
+ /*
+ * o2hb_live_lock was dropped across o2nm_depend_item().
+ * o2hb_set_quorum_device() runs in the heartbeat thread
+ * without o2hb_dependency_mutex, so for global heartbeat
+ * it may have crossed O2HB_PIN_CUT_OFF and unpinned the
+ * regions while we slept. If that happened this pin is
+ * no longer wanted; undo it and stop rather than
+ * resurrecting it on the rescan below.
+ */
+ if (!region_uuid &&
+ bitmap_weight(o2hb_quorum_region_bitmap,
+ O2NM_MAX_REGIONS) > O2HB_PIN_CUT_OFF) {
+ o2nm_undepend_item(&pinned->hr_item);
+ spin_unlock(&o2hb_live_lock);
+ config_item_put(&pinned->hr_item);
+ spin_lock(&o2hb_live_lock);
break;
}
+ mlog(ML_CLUSTER, "Pin region %s\n", uuid);
+ pinned->hr_item_pinned = 1;
+ } else if (ret == -ENOENT && (found || !region_uuid)) {
+ /*
+ * For local hb (found): ignore ENOENT from userdlm
+ * domains as before. For global hb (!region_uuid):
+ * the region may have been detached from configfs
+ * while the lock was dropped — skip it and continue
+ * pinning the remaining regions.
+ */
+ ret = 0;
+ } else {
+ mlog(ML_ERROR, "Pin region %s fails with %d\n",
+ uuid, ret);
}
-skip_pin:
- if (found)
- break;
- }
+
+ /*
+ * config_item_put() may drop the last reference and run
+ * o2hb_region_release(), which also grabs o2hb_live_lock and
+ * can sleep, so it must happen with the lock released.
+ */
+ spin_unlock(&o2hb_live_lock);
+ config_item_put(&pinned->hr_item);
+ spin_lock(&o2hb_live_lock);
+
+ /* local hb pins a single matching region */
+ } while (!ret && !region_uuid);
return ret;
}
@@ -2376,12 +2544,13 @@ static int o2hb_region_inc_user(const char *region_uuid)
{
int ret = 0;
+ mutex_lock(&o2hb_dependency_mutex);
spin_lock(&o2hb_live_lock);
/* local heartbeat */
if (!o2hb_global_heartbeat_active()) {
- ret = o2hb_region_pin(region_uuid);
- goto unlock;
+ ret = o2hb_region_pin(region_uuid, false);
+ goto unlock;
}
/*
@@ -2393,16 +2562,23 @@ static int o2hb_region_inc_user(const char *region_uuid)
goto unlock;
if (bitmap_weight(o2hb_quorum_region_bitmap,
- O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF)
- ret = o2hb_region_pin(NULL);
+ O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF) {
+ ret = o2hb_region_pin(NULL, false);
+ if (ret) {
+ o2hb_region_unpin(NULL);
+ o2hb_dependent_users--;
+ }
+ }
unlock:
spin_unlock(&o2hb_live_lock);
+ mutex_unlock(&o2hb_dependency_mutex);
return ret;
}
static void o2hb_region_dec_user(const char *region_uuid)
{
+ mutex_lock(&o2hb_dependency_mutex);
spin_lock(&o2hb_live_lock);
/* local heartbeat */
@@ -2421,6 +2597,7 @@ static void o2hb_region_dec_user(const char *region_uuid)
unlock:
spin_unlock(&o2hb_live_lock);
+ mutex_unlock(&o2hb_dependency_mutex);
}
int o2hb_register_callback(const char *region_uuid,
@@ -2495,7 +2672,7 @@ int o2hb_check_node_heartbeating_no_sem(u8 node_num)
unsigned long testing_map[BITS_TO_LONGS(O2NM_MAX_NODES)];
spin_lock(&o2hb_live_lock);
- o2hb_fill_node_map_from_callback(testing_map, O2NM_MAX_NODES);
+ __o2hb_fill_node_map(testing_map, O2NM_MAX_NODES);
spin_unlock(&o2hb_live_lock);
if (!test_bit(node_num, testing_map)) {
mlog(ML_HEARTBEAT,
@@ -2512,7 +2689,7 @@ int o2hb_check_node_heartbeating_from_callback(u8 node_num)
{
unsigned long testing_map[BITS_TO_LONGS(O2NM_MAX_NODES)];
- o2hb_fill_node_map_from_callback(testing_map, O2NM_MAX_NODES);
+ o2hb_fill_node_map_locked(testing_map, O2NM_MAX_NODES);
if (!test_bit(node_num, testing_map)) {
mlog(ML_HEARTBEAT,
"node (%u) does not have heartbeating enabled.\n",
diff --git a/fs/ocfs2/cluster/heartbeat.h b/fs/ocfs2/cluster/heartbeat.h
index 8ef8c1b9eeb7..2ca2b657583c 100644
--- a/fs/ocfs2/cluster/heartbeat.h
+++ b/fs/ocfs2/cluster/heartbeat.h
@@ -58,6 +58,11 @@ int o2hb_register_callback(const char *region_uuid,
struct o2hb_callback_func *hc);
void o2hb_unregister_callback(const char *region_uuid,
struct o2hb_callback_func *hc);
+void o2hb_callback_read_lock(void);
+void o2hb_callback_read_unlock(void);
+void o2hb_synchronize_callbacks(void);
+void o2hb_fill_node_map_locked(unsigned long *map,
+ unsigned int bits);
void o2hb_fill_node_map(unsigned long *map,
unsigned int bits);
void o2hb_exit(void);
diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c
index 402563154550..e08850a5d736 100644
--- a/fs/ocfs2/cluster/nodemanager.c
+++ b/fs/ocfs2/cluster/nodemanager.c
@@ -326,6 +326,7 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page,
struct o2nm_node *node = to_o2nm_node(item);
struct o2nm_cluster *cluster;
unsigned long tmp;
+ bool starting = false;
char *p = (char *)page;
ssize_t ret;
@@ -362,11 +363,13 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page,
ret = o2net_start_listening(node);
if (ret)
goto out;
+ starting = true;
}
if (!tmp && cluster->cl_has_local &&
cluster->cl_local_node == node->nd_num) {
o2net_stop_listening(node);
+ cluster->cl_has_local = 0;
cluster->cl_local_node = O2NM_INVALID_NODE_NUM;
}
@@ -374,6 +377,8 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page,
if (node->nd_local) {
cluster->cl_has_local = tmp;
cluster->cl_local_node = node->nd_num;
+ if (starting)
+ o2net_complete_start_listening(node);
}
ret = count;
@@ -777,17 +782,23 @@ int o2nm_depend_item(struct config_item *item)
return configfs_depend_item(&o2nm_cluster_group.cs_subsys, item);
}
+int o2nm_depend_item_unlocked(struct config_item *item)
+{
+ return configfs_depend_item_unlocked(&o2nm_cluster_group.cs_subsys,
+ item);
+}
+
void o2nm_undepend_item(struct config_item *item)
{
configfs_undepend_item(item);
}
-int o2nm_depend_this_node(void)
+int o2nm_depend_node(u8 node_num)
{
int ret = 0;
struct o2nm_node *local_node;
- local_node = o2nm_get_node_by_num(o2nm_this_node());
+ local_node = o2nm_get_node_by_num(node_num);
if (!local_node) {
ret = -EINVAL;
goto out;
@@ -800,17 +811,27 @@ out:
return ret;
}
-void o2nm_undepend_this_node(void)
+void o2nm_undepend_node(u8 node_num)
{
struct o2nm_node *local_node;
- local_node = o2nm_get_node_by_num(o2nm_this_node());
+ local_node = o2nm_get_node_by_num(node_num);
BUG_ON(!local_node);
o2nm_undepend_item(&local_node->nd_item);
o2nm_node_put(local_node);
}
+int o2nm_depend_this_node(void)
+{
+ return o2nm_depend_node(o2nm_this_node());
+}
+
+void o2nm_undepend_this_node(void)
+{
+ o2nm_undepend_node(o2nm_this_node());
+}
+
static void __exit exit_o2nm(void)
{
diff --git a/fs/ocfs2/cluster/nodemanager.h b/fs/ocfs2/cluster/nodemanager.h
index 3490e77a952d..ca3483fb5450 100644
--- a/fs/ocfs2/cluster/nodemanager.h
+++ b/fs/ocfs2/cluster/nodemanager.h
@@ -64,7 +64,10 @@ void o2nm_node_get(struct o2nm_node *node);
void o2nm_node_put(struct o2nm_node *node);
int o2nm_depend_item(struct config_item *item);
+int o2nm_depend_item_unlocked(struct config_item *item);
void o2nm_undepend_item(struct config_item *item);
+int o2nm_depend_node(u8 node_num);
+void o2nm_undepend_node(u8 node_num);
int o2nm_depend_this_node(void);
void o2nm_undepend_this_node(void);
diff --git a/fs/ocfs2/cluster/tcp.c b/fs/ocfs2/cluster/tcp.c
index 09a1f3b77bb8..474fe1414cee 100644
--- a/fs/ocfs2/cluster/tcp.c
+++ b/fs/ocfs2/cluster/tcp.c
@@ -38,6 +38,8 @@
*/
#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
#include <linux/sched/mm.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
@@ -102,9 +104,16 @@ static struct socket *o2net_listen_sock;
* quorum work is queued as sock containers are shutdown.. stop_listening
* tears down all the node's sock containers, preventing future shutdowns
* and queued quorum work, before canceling delayed quorum work and
- * destroying the work queue.
+ * destroying the work queue. Handler teardown can also race local listener
+ * shutdown, so keep a waitable destroying pointer until the old ordered
+ * queue has finished draining.
*/
static struct workqueue_struct *o2net_wq;
+static struct workqueue_struct *o2net_wq_destroying;
+static DEFINE_MUTEX(o2net_wq_mutex);
+static DECLARE_COMPLETION(o2net_wq_destroyed);
+/* Heartbeat callbacks stay registered across local-node off/on. */
+static bool o2net_listening;
static struct work_struct o2net_listen_work;
static struct o2hb_callback_func o2net_hb_up, o2net_hb_down;
@@ -884,6 +893,27 @@ void o2net_unregister_handler_list(struct list_head *list)
}
EXPORT_SYMBOL_GPL(o2net_unregister_handler_list);
+static void o2net_flush_wq(void)
+{
+ mutex_lock(&o2net_wq_mutex);
+ if (o2net_wq_destroying) {
+ mutex_unlock(&o2net_wq_mutex);
+ wait_for_completion(&o2net_wq_destroyed);
+ return;
+ }
+
+ if (o2net_wq)
+ flush_workqueue(o2net_wq);
+ mutex_unlock(&o2net_wq_mutex);
+}
+
+void o2net_unregister_and_flush_handler_list(struct list_head *list)
+{
+ o2net_unregister_handler_list(list);
+ o2net_flush_wq();
+}
+EXPORT_SYMBOL_GPL(o2net_unregister_and_flush_handler_list);
+
static struct o2net_msg_handler *o2net_handler_get(u32 msg_type, u32 key)
{
struct o2net_msg_handler *nmh;
@@ -1692,6 +1722,19 @@ static void o2net_still_up(struct work_struct *work)
/* ------------------------------------------------------------ */
+static void o2net_hb_node_up(struct o2net_node *nn)
+{
+ /* ensure an immediate connect attempt */
+ nn->nn_last_connect_attempt = jiffies -
+ (msecs_to_jiffies(o2net_reconnect_delay()) + 1);
+
+ spin_lock(&nn->nn_lock);
+ atomic_set(&nn->nn_timeout, 0);
+ if (nn->nn_persistent_error)
+ o2net_set_nn_state(nn, NULL, 0, 0);
+ spin_unlock(&nn->nn_lock);
+}
+
void o2net_disconnect_node(struct o2nm_node *node)
{
struct o2net_node *nn = o2net_nn_from_num(node->nd_num);
@@ -1702,52 +1745,48 @@ void o2net_disconnect_node(struct o2nm_node *node)
o2net_set_nn_state(nn, NULL, 0, -ENOTCONN);
spin_unlock(&nn->nn_lock);
- if (o2net_wq) {
- cancel_delayed_work(&nn->nn_connect_expired);
- cancel_delayed_work(&nn->nn_connect_work);
- cancel_delayed_work(&nn->nn_still_up);
- flush_workqueue(o2net_wq);
- }
+ cancel_delayed_work(&nn->nn_connect_expired);
+ cancel_delayed_work(&nn->nn_connect_work);
+ cancel_delayed_work(&nn->nn_still_up);
+ o2net_flush_wq();
}
static void o2net_hb_node_down_cb(struct o2nm_node *node, int node_num,
void *data)
{
+ u8 this_node;
+
o2quo_hb_down(node_num);
if (!node)
- return;
+ goto out;
+
+ this_node = o2nm_this_node();
+ if (!READ_ONCE(o2net_listening) || this_node == O2NM_MAX_NODES)
+ goto out;
- if (node_num != o2nm_this_node())
+ if (node_num != this_node)
o2net_disconnect_node(node);
+out:
BUG_ON(atomic_read(&o2net_connected_peers) < 0);
}
static void o2net_hb_node_up_cb(struct o2nm_node *node, int node_num,
void *data)
{
- struct o2net_node *nn = o2net_nn_from_num(node_num);
+ u8 this_node;
o2quo_hb_up(node_num);
BUG_ON(!node);
- /* ensure an immediate connect attempt */
- nn->nn_last_connect_attempt = jiffies -
- (msecs_to_jiffies(o2net_reconnect_delay()) + 1);
+ this_node = o2nm_this_node();
+ if (!READ_ONCE(o2net_listening) || this_node == O2NM_MAX_NODES)
+ return;
- if (node_num != o2nm_this_node()) {
- /* believe it or not, accept and node heartbeating testing
- * can succeed for this node before we got here.. so
- * only use set_nn_state to clear the persistent error
- * if that hasn't already happened */
- spin_lock(&nn->nn_lock);
- atomic_set(&nn->nn_timeout, 0);
- if (nn->nn_persistent_error)
- o2net_set_nn_state(nn, NULL, 0, 0);
- spin_unlock(&nn->nn_lock);
- }
+ if (node_num != this_node)
+ o2net_hb_node_up(o2net_nn_from_num(node_num));
}
void o2net_unregister_hb_callbacks(void)
@@ -1756,6 +1795,37 @@ void o2net_unregister_hb_callbacks(void)
o2hb_unregister_callback(NULL, &o2net_hb_down);
}
+/*
+ * Delay heartbeat-driven network work until the local node is fully published
+ * through o2nm_this_node(), then replay the nodes that are already live while
+ * callback delivery stays blocked.
+ */
+void o2net_complete_start_listening(struct o2nm_node *node)
+{
+ unsigned long live_nodes[BITS_TO_LONGS(O2NM_MAX_NODES)];
+ unsigned long node_num;
+ u8 local_node;
+
+ local_node = o2nm_this_node();
+ if (WARN_ON_ONCE(local_node == O2NM_MAX_NODES))
+ return;
+ if (WARN_ON_ONCE(local_node != node->nd_num))
+ return;
+ if (WARN_ON_ONCE(!o2net_wq))
+ return;
+
+ o2hb_callback_read_lock();
+ WRITE_ONCE(o2net_listening, true);
+ o2hb_fill_node_map_locked(live_nodes, O2NM_MAX_NODES);
+ for_each_set_bit(node_num, live_nodes, O2NM_MAX_NODES) {
+ if (node_num == local_node)
+ continue;
+
+ o2net_hb_node_up(o2net_nn_from_num(node_num));
+ }
+ o2hb_callback_read_unlock();
+}
+
int o2net_register_hb_callbacks(void)
{
int ret;
@@ -2023,6 +2093,36 @@ out:
return ret;
}
+static void o2net_destroy_wq(void)
+{
+ struct workqueue_struct *wq;
+
+ mutex_lock(&o2net_wq_mutex);
+ if (o2net_wq_destroying) {
+ mutex_unlock(&o2net_wq_mutex);
+ wait_for_completion(&o2net_wq_destroyed);
+ return;
+ }
+
+ wq = o2net_wq;
+ if (!wq) {
+ mutex_unlock(&o2net_wq_mutex);
+ return;
+ }
+
+ reinit_completion(&o2net_wq_destroyed);
+ o2net_wq_destroying = wq;
+ mutex_unlock(&o2net_wq_mutex);
+
+ destroy_workqueue(wq);
+
+ mutex_lock(&o2net_wq_mutex);
+ o2net_wq = NULL;
+ o2net_wq_destroying = NULL;
+ complete_all(&o2net_wq_destroyed);
+ mutex_unlock(&o2net_wq_mutex);
+}
+
/*
* called from node manager when we should bring up our network listening
* socket. node manager handles all the serialization to only call this
@@ -2033,22 +2133,44 @@ out:
int o2net_start_listening(struct o2nm_node *node)
{
int ret = 0;
+ struct workqueue_struct *wq;
+
+ if (WARN_ON_ONCE(READ_ONCE(o2net_listening)))
+ return -EBUSY;
+
+ mutex_lock(&o2net_wq_mutex);
+ if (o2net_wq_destroying) {
+ mutex_unlock(&o2net_wq_mutex);
+ return -EBUSY;
+ }
+ if (WARN_ON_ONCE(o2net_wq)) {
+ mutex_unlock(&o2net_wq_mutex);
+ return -EBUSY;
+ }
+ mutex_unlock(&o2net_wq_mutex);
- BUG_ON(o2net_wq != NULL);
BUG_ON(o2net_listen_sock != NULL);
mlog(ML_KTHREAD, "starting o2net thread...\n");
- o2net_wq = alloc_ordered_workqueue("o2net", WQ_MEM_RECLAIM);
- if (o2net_wq == NULL) {
+ wq = alloc_ordered_workqueue("o2net", WQ_MEM_RECLAIM);
+ if (!wq) {
mlog(ML_ERROR, "unable to launch o2net thread\n");
return -ENOMEM; /* ? */
}
+ mutex_lock(&o2net_wq_mutex);
+ if (unlikely(o2net_wq_destroying || o2net_wq)) {
+ mutex_unlock(&o2net_wq_mutex);
+ destroy_workqueue(wq);
+ return -EBUSY;
+ }
+ o2net_wq = wq;
+ mutex_unlock(&o2net_wq_mutex);
+
ret = o2net_open_listening_sock(node->nd_ipv4_address,
node->nd_ipv4_port);
if (ret) {
- destroy_workqueue(o2net_wq);
- o2net_wq = NULL;
+ o2net_destroy_wq();
} else
o2quo_conn_up(node->nd_num);
@@ -2065,6 +2187,9 @@ void o2net_stop_listening(struct o2nm_node *node)
BUG_ON(o2net_wq == NULL);
BUG_ON(o2net_listen_sock == NULL);
+ WRITE_ONCE(o2net_listening, false);
+ o2hb_synchronize_callbacks();
+
/* stop the listening socket from generating work */
write_lock_bh(&sock->sk->sk_callback_lock);
sock->sk->sk_data_ready = sock->sk->sk_user_data;
@@ -2081,8 +2206,7 @@ void o2net_stop_listening(struct o2nm_node *node)
/* finish all work and tear down the work queue */
mlog(ML_KTHREAD, "waiting for o2net thread to exit....\n");
- destroy_workqueue(o2net_wq);
- o2net_wq = NULL;
+ o2net_destroy_wq();
sock_release(o2net_listen_sock);
o2net_listen_sock = NULL;
diff --git a/fs/ocfs2/cluster/tcp.h b/fs/ocfs2/cluster/tcp.h
index a75b551d31c7..a11bcee28947 100644
--- a/fs/ocfs2/cluster/tcp.h
+++ b/fs/ocfs2/cluster/tcp.h
@@ -89,6 +89,7 @@ int o2net_register_handler(u32 msg_type, u32 key, u32 max_len,
o2net_post_msg_handler_func *post_func,
struct list_head *unreg_list);
void o2net_unregister_handler_list(struct list_head *list);
+void o2net_unregister_and_flush_handler_list(struct list_head *list);
void o2net_fill_node_map(unsigned long *map, unsigned bytes);
@@ -96,6 +97,7 @@ struct o2nm_node;
int o2net_register_hb_callbacks(void);
void o2net_unregister_hb_callbacks(void);
int o2net_start_listening(struct o2nm_node *node);
+void o2net_complete_start_listening(struct o2nm_node *node);
void o2net_stop_listening(struct o2nm_node *node);
void o2net_disconnect_node(struct o2nm_node *node);
int o2net_num_connected_peers(void);
diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
index 8e6b03238327..0075e1624310 100644
--- a/fs/ocfs2/dir.c
+++ b/fs/ocfs2/dir.c
@@ -302,10 +302,11 @@ static int ocfs2_check_dir_entry(struct inode *dir,
unsigned long offset)
{
const char *error_msg = NULL;
+ unsigned long buf_offset = (char *)de - buf;
unsigned long next_offset;
int rlen;
- if (offset > size - OCFS2_DIR_REC_LEN(1)) {
+ if (buf_offset > size || size - buf_offset < OCFS2_DIR_REC_LEN(1)) {
/* Dirent is (maybe partially) beyond the buffer
* boundaries so touching 'de' members is unsafe.
*/
@@ -316,7 +317,7 @@ static int ocfs2_check_dir_entry(struct inode *dir,
}
rlen = le16_to_cpu(de->rec_len);
- next_offset = ((char *) de - buf) + rlen;
+ next_offset = buf_offset + rlen;
if (unlikely(rlen < OCFS2_DIR_REC_LEN(1)))
error_msg = "rec_len is smaller than minimal";
@@ -624,6 +625,28 @@ static int ocfs2_validate_dx_root(struct super_block *sb,
le16_to_cpu(el->l_count));
goto bail;
}
+ } else {
+ struct ocfs2_dx_entry_list *dl_list = &dx_root->dr_entries;
+
+ if (le16_to_cpu(dl_list->de_count) !=
+ ocfs2_dx_entries_per_root(sb)) {
+ ret = ocfs2_error(sb,
+ "Dir Index Root # %llu has invalid de_count %u (expected %u)\n",
+ (unsigned long long)le64_to_cpu(dx_root->dr_blkno),
+ le16_to_cpu(dl_list->de_count),
+ ocfs2_dx_entries_per_root(sb));
+ goto bail;
+ }
+
+ if (le16_to_cpu(dl_list->de_num_used) >
+ le16_to_cpu(dl_list->de_count)) {
+ ret = ocfs2_error(sb,
+ "Dir Index Root # %llu has invalid de_num_used %u (de_count %u)\n",
+ (unsigned long long)le64_to_cpu(dx_root->dr_blkno),
+ le16_to_cpu(dl_list->de_num_used),
+ le16_to_cpu(dl_list->de_count));
+ goto bail;
+ }
}
bail:
@@ -663,10 +686,25 @@ static int ocfs2_validate_dx_leaf(struct super_block *sb,
return ret;
}
- if (!OCFS2_IS_VALID_DX_LEAF(dx_leaf)) {
- ret = ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n",
- 7, dx_leaf->dl_signature);
- }
+ if (!OCFS2_IS_VALID_DX_LEAF(dx_leaf))
+ return ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n",
+ 7, dx_leaf->dl_signature);
+
+ if (le16_to_cpu(dx_leaf->dl_list.de_count) !=
+ ocfs2_dx_entries_per_leaf(sb))
+ return ocfs2_error(sb,
+ "Dir Index Leaf # %llu has invalid de_count %u (expected %u)\n",
+ (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno),
+ le16_to_cpu(dx_leaf->dl_list.de_count),
+ ocfs2_dx_entries_per_leaf(sb));
+
+ if (le16_to_cpu(dx_leaf->dl_list.de_num_used) >
+ le16_to_cpu(dx_leaf->dl_list.de_count))
+ return ocfs2_error(sb,
+ "Dir Index Leaf # %llu has invalid de_num_used %u (de_count %u)\n",
+ (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno),
+ le16_to_cpu(dx_leaf->dl_list.de_num_used),
+ le16_to_cpu(dx_leaf->dl_list.de_count));
return ret;
}
@@ -1811,7 +1849,12 @@ static int ocfs2_dir_foreach_blk_id(struct inode *inode,
* dirent right now. Scan from the start of the block
* to make sure. */
if (!inode_eq_iversion(inode, *f_version)) {
- for (i = 0; i < i_size_read(inode) && i < offset; ) {
+ loff_t size = i_size_read(inode);
+
+ for (i = 0; i + OCFS2_DIR_REC_LEN(1) <= size &&
+ i < offset;) {
+ unsigned int rec_len;
+
de = (struct ocfs2_dir_entry *)
(data->id_data + i);
/* It's too expensive to do a full
@@ -1820,10 +1863,11 @@ static int ocfs2_dir_foreach_blk_id(struct inode *inode,
* least that it is non-zero. A
* failure will be detected in the
* dirent test below. */
- if (le16_to_cpu(de->rec_len) <
- OCFS2_DIR_REC_LEN(1))
+ rec_len = le16_to_cpu(de->rec_len);
+ if (rec_len < OCFS2_DIR_REC_LEN(1) ||
+ i + rec_len > size)
break;
- i += le16_to_cpu(de->rec_len);
+ i += rec_len;
}
ctx->pos = offset = i;
*f_version = inode_query_iversion(inode);
@@ -1866,6 +1910,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
struct super_block * sb = inode->i_sb;
unsigned int ra_sectors = 16;
int stored = 0;
+ int ret;
bh = NULL;
@@ -1873,9 +1918,13 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
while (ctx->pos < i_size_read(inode)) {
blk = ctx->pos >> sb->s_blocksize_bits;
- if (ocfs2_read_dir_block(inode, blk, &bh, 0)) {
+ ret = ocfs2_read_dir_block(inode, blk, &bh, 0);
+ if (ret) {
+ if (persist)
+ return ret;
/* Skip the corrupt dirblock and keep trying */
ctx->pos += sb->s_blocksize - offset;
+ offset = 0;
continue;
}
@@ -1902,7 +1951,10 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
* dirent right now. Scan from the start of the block
* to make sure. */
if (!inode_eq_iversion(inode, *f_version)) {
- for (i = 0; i < sb->s_blocksize && i < offset; ) {
+ for (i = 0; i + OCFS2_DIR_REC_LEN(1) <= sb->s_blocksize &&
+ i < offset;) {
+ unsigned int rec_len;
+
de = (struct ocfs2_dir_entry *) (bh->b_data + i);
/* It's too expensive to do a full
* dirent test each time round this
@@ -1910,13 +1962,14 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
* least that it is non-zero. A
* failure will be detected in the
* dirent test below. */
- if (le16_to_cpu(de->rec_len) <
- OCFS2_DIR_REC_LEN(1))
+ rec_len = le16_to_cpu(de->rec_len);
+ if (rec_len < OCFS2_DIR_REC_LEN(1) ||
+ i + rec_len > sb->s_blocksize)
break;
- i += le16_to_cpu(de->rec_len);
+ i += rec_len;
}
offset = i;
- ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1))
+ ctx->pos = (ctx->pos & ~((loff_t)sb->s_blocksize - 1))
| offset;
*f_version = inode_query_iversion(inode);
}
@@ -1969,8 +2022,7 @@ static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version,
int ocfs2_dir_foreach(struct inode *inode, struct dir_context *ctx)
{
u64 version = inode_query_iversion(inode);
- ocfs2_dir_foreach_blk(inode, &version, ctx, true);
- return 0;
+ return ocfs2_dir_foreach_blk(inode, &version, ctx, true);
}
/*
@@ -2167,7 +2219,7 @@ out:
/*
* routine to check that the specified directory is empty (for rmdir)
*
- * Returns 1 if dir is empty, zero otherwise.
+ * Returns 1 if dir is empty, zero if not, and a negative errno on error.
*
* XXX: This is a performance problem for unindexed directories.
*/
@@ -2180,8 +2232,10 @@ int ocfs2_empty_dir(struct inode *inode)
if (ocfs2_dir_indexed(inode)) {
ret = ocfs2_empty_dir_dx(inode, &priv);
- if (ret)
+ if (ret) {
mlog_errno(ret);
+ return ret;
+ }
/*
* We still run ocfs2_dir_foreach to get the checks
* for "." and "..".
@@ -2189,8 +2243,10 @@ int ocfs2_empty_dir(struct inode *inode)
}
ret = ocfs2_dir_foreach(inode, &priv.ctx);
- if (ret)
+ if (ret) {
mlog_errno(ret);
+ return ret;
+ }
if (!priv.seen_dot || !priv.seen_dot_dot) {
mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n",
diff --git a/fs/ocfs2/dlm/dlmdebug.c b/fs/ocfs2/dlm/dlmdebug.c
index fe4fdd09bae3..ce6d301cb942 100644
--- a/fs/ocfs2/dlm/dlmdebug.c
+++ b/fs/ocfs2/dlm/dlmdebug.c
@@ -260,10 +260,10 @@ void dlm_print_one_mle(struct dlm_master_list_entry *mle)
{
char *buf;
- buf = (char *) get_zeroed_page(GFP_ATOMIC);
+ buf = kzalloc(PAGE_SIZE, GFP_ATOMIC);
if (buf) {
dump_mle(mle, buf, PAGE_SIZE - 1);
- free_page((unsigned long)buf);
+ kfree(buf);
}
}
@@ -280,7 +280,7 @@ static struct dentry *dlm_debugfs_root;
/* begin - utils funcs */
static int debug_release(struct inode *inode, struct file *file)
{
- free_page((unsigned long)file->private_data);
+ kfree(file->private_data);
return 0;
}
@@ -327,17 +327,15 @@ static int debug_purgelist_open(struct inode *inode, struct file *file)
struct dlm_ctxt *dlm = inode->i_private;
char *buf = NULL;
- buf = (char *) get_zeroed_page(GFP_NOFS);
+ buf = kzalloc(PAGE_SIZE, GFP_NOFS);
if (!buf)
- goto bail;
+ return -ENOMEM;
i_size_write(inode, debug_purgelist_print(dlm, buf, PAGE_SIZE - 1));
file->private_data = buf;
return 0;
-bail:
- return -ENOMEM;
}
static const struct file_operations debug_purgelist_fops = {
@@ -384,17 +382,15 @@ static int debug_mle_open(struct inode *inode, struct file *file)
struct dlm_ctxt *dlm = inode->i_private;
char *buf = NULL;
- buf = (char *) get_zeroed_page(GFP_NOFS);
+ buf = kzalloc(PAGE_SIZE, GFP_NOFS);
if (!buf)
- goto bail;
+ return -ENOMEM;
i_size_write(inode, debug_mle_print(dlm, buf, PAGE_SIZE - 1));
file->private_data = buf;
return 0;
-bail:
- return -ENOMEM;
}
static const struct file_operations debug_mle_fops = {
@@ -560,6 +556,7 @@ static int debug_lockres_open(struct inode *inode, struct file *file)
struct dlm_ctxt *dlm = inode->i_private;
struct debug_lockres *dl;
void *buf;
+ int status = -ENOMEM;
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
@@ -572,16 +569,23 @@ static int debug_lockres_open(struct inode *inode, struct file *file)
dl->dl_len = PAGE_SIZE;
dl->dl_buf = buf;
- dlm_grab(dlm);
- dl->dl_ctxt = dlm;
+ /* ->release uses dl_ctxt after open, so it needs a real pin. */
+ dl->dl_ctxt = dlm_grab(dlm);
+ if (!dl->dl_ctxt) {
+ status = -ENOENT;
+ goto bailseq;
+ }
return 0;
+bailseq:
+ seq_release_private(inode, file);
bailfree:
kfree(buf);
bail:
- mlog_errno(-ENOMEM);
- return -ENOMEM;
+ if (status != -ENOENT)
+ mlog_errno(status);
+ return status;
}
static int debug_lockres_release(struct inode *inode, struct file *file)
@@ -775,17 +779,15 @@ static int debug_state_open(struct inode *inode, struct file *file)
struct dlm_ctxt *dlm = inode->i_private;
char *buf = NULL;
- buf = (char *) get_zeroed_page(GFP_NOFS);
+ buf = kzalloc(PAGE_SIZE, GFP_NOFS);
if (!buf)
- goto bail;
+ return -ENOMEM;
i_size_write(inode, debug_state_print(dlm, buf, PAGE_SIZE - 1));
file->private_data = buf;
return 0;
-bail:
- return -ENOMEM;
}
static const struct file_operations debug_state_fops = {
diff --git a/fs/ocfs2/dlm/dlmdomain.c b/fs/ocfs2/dlm/dlmdomain.c
index dc9da9133c8e..97bb9400e24b 100644
--- a/fs/ocfs2/dlm/dlmdomain.c
+++ b/fs/ocfs2/dlm/dlmdomain.c
@@ -63,7 +63,7 @@ static inline void byte_copymap(u8 dmap[], unsigned long smap[],
static void dlm_free_pagevec(void **vec, int pages)
{
while (pages--)
- free_page((unsigned long)vec[pages]);
+ kfree(vec[pages]);
kfree(vec);
}
@@ -75,9 +75,11 @@ static void **dlm_alloc_pagevec(int pages)
if (!vec)
return NULL;
- for (i = 0; i < pages; i++)
- if (!(vec[i] = (void *)__get_free_page(GFP_KERNEL)))
+ for (i = 0; i < pages; i++) {
+ vec[i] = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!vec[i])
goto out_free;
+ }
mlog(0, "Allocated DLM hash pagevec; %d pages (%lu expected), %lu buckets per page\n",
pages, (unsigned long)DLM_HASH_PAGES,
diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c
index 93eff38fdadd..612969867ff9 100644
--- a/fs/ocfs2/dlm/dlmmaster.c
+++ b/fs/ocfs2/dlm/dlmmaster.c
@@ -2548,7 +2548,7 @@ static int dlm_migrate_lockres(struct dlm_ctxt *dlm,
/* preallocate up front. if this fails, abort */
ret = -ENOMEM;
- mres = (struct dlm_migratable_lockres *) __get_free_page(GFP_NOFS);
+ mres = kmalloc(PAGE_SIZE, GFP_NOFS);
if (!mres) {
mlog_errno(ret);
goto leave;
@@ -2725,8 +2725,7 @@ leave:
if (wake)
wake_up(&res->wq);
- if (mres)
- free_page((unsigned long)mres);
+ kfree(mres);
dlm_put(dlm);
@@ -3100,6 +3099,12 @@ int dlm_migrate_request_handler(struct o2net_msg *msg, u32 len, void *data,
name = migrate->name;
namelen = migrate->namelen;
+ if (namelen > DLM_LOCKID_NAME_MAX) {
+ mlog(ML_ERROR, "%s: invalid name length %u in migrate request\n",
+ dlm->name, namelen);
+ ret = -EINVAL;
+ goto leave;
+ }
hash = dlm_lockid_hash(name, namelen);
/* preallocate.. if this fails, abort */
diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c
index 128872bd945d..9d4a2695b959 100644
--- a/fs/ocfs2/dlm/dlmrecovery.c
+++ b/fs/ocfs2/dlm/dlmrecovery.c
@@ -837,7 +837,7 @@ int dlm_request_all_locks_handler(struct o2net_msg *msg, u32 len, void *data,
}
/* this will get freed by dlm_request_all_locks_worker */
- buf = (char *) __get_free_page(GFP_NOFS);
+ buf = kmalloc(PAGE_SIZE, GFP_NOFS);
if (!buf) {
kfree(item);
dlm_put(dlm);
@@ -933,7 +933,7 @@ static void dlm_request_all_locks_worker(struct dlm_work_item *item, void *data)
}
}
leave:
- free_page((unsigned long)data);
+ kfree(data);
}
@@ -1357,6 +1357,15 @@ int dlm_mig_lockres_handler(struct o2net_msg *msg, u32 len, void *data,
if (!dlm_grab(dlm))
return -EINVAL;
+ if (mres->lockname_len > DLM_LOCKID_NAME_MAX ||
+ mres->num_locks > DLM_MAX_MIGRATABLE_LOCKS ||
+ be16_to_cpu(msg->data_len) < struct_size(mres, ml, mres->num_locks)) {
+ mlog(ML_ERROR, "%s: invalid lockres migration message from %u\n",
+ dlm->name, mres->master);
+ dlm_put(dlm);
+ return -EINVAL;
+ }
+
if (!dlm_joined(dlm)) {
mlog(ML_ERROR, "Domain %s not joined! "
"lockres %.*s, master %u\n",
diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c
index 5821e33df78f..53df5dd10ad0 100644
--- a/fs/ocfs2/dlmfs/dlmfs.c
+++ b/fs/ocfs2/dlmfs/dlmfs.c
@@ -422,7 +422,7 @@ static struct dentry *dlmfs_mkdir(struct mnt_idmap * idmap,
goto bail;
}
- inode = dlmfs_get_inode(dir, dentry, mode | S_IFDIR);
+ inode = dlmfs_get_inode(dir, dentry, mode);
if (!inode) {
status = -ENOMEM;
mlog_errno(status);
@@ -453,8 +453,7 @@ bail:
static int dlmfs_create(struct mnt_idmap *idmap,
struct inode *dir,
struct dentry *dentry,
- umode_t mode,
- bool excl)
+ umode_t mode)
{
int status = 0;
struct inode *inode;
diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c
index 7283bb2c5a31..a23dd8f86c89 100644
--- a/fs/ocfs2/dlmglue.c
+++ b/fs/ocfs2/dlmglue.c
@@ -3134,6 +3134,22 @@ static void *ocfs2_dlm_seq_next(struct seq_file *m, void *v, loff_t *pos)
* - Add last pr/ex unlock times and first lock wait time in usecs
*/
#define OCFS2_DLM_DEBUG_STR_VERSION 4
+
+/*
+ * The debug iterator snapshots lockres by value, so a userspace-stack LVB
+ * pointer copied from the original lockres must be rebased to the copied
+ * lksb before the dump walks the raw bytes.
+ */
+static void ocfs2_dlm_seq_rebase_lvb(struct ocfs2_lock_res *lockres)
+{
+ if (!ocfs2_stack_supports_plocks())
+ return;
+
+ if (lockres->l_lksb.lksb_fsdlm.sb_lvbptr)
+ lockres->l_lksb.lksb_fsdlm.sb_lvbptr =
+ (char *)&lockres->l_lksb + sizeof(struct dlm_lksb);
+}
+
static int ocfs2_dlm_seq_show(struct seq_file *m, void *v)
{
int i;
@@ -3191,6 +3207,7 @@ static int ocfs2_dlm_seq_show(struct seq_file *m, void *v)
lockres->l_blocking);
/* Dump the raw LVB */
+ ocfs2_dlm_seq_rebase_lvb(lockres);
lvb = ocfs2_dlm_lvb(&lockres->l_lksb);
for(i = 0; i < DLM_LVB_LEN; i++)
seq_printf(m, "0x%x\t", lvb[i]);
diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
index 7df9921c1a38..d6e977ba6565 100644
--- a/fs/ocfs2/file.c
+++ b/fs/ocfs2/file.c
@@ -444,21 +444,26 @@ int ocfs2_truncate_file(struct inode *inode,
struct ocfs2_dinode *fe = NULL;
struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
- /* We trust di_bh because it comes from ocfs2_inode_lock(), which
- * already validated it */
+ /*
+ * On local mounts ocfs2_inode_lock_update() skips the inode
+ * refresh path, so truncation still needs to reject an inode
+ * state that no longer matches di_bh.
+ */
fe = (struct ocfs2_dinode *) di_bh->b_data;
trace_ocfs2_truncate_file((unsigned long long)OCFS2_I(inode)->ip_blkno,
(unsigned long long)le64_to_cpu(fe->i_size),
(unsigned long long)new_i_size);
- mlog_bug_on_msg(le64_to_cpu(fe->i_size) != i_size_read(inode),
- "Inode %llu, inode i_size = %lld != di "
- "i_size = %llu, i_flags = 0x%x\n",
- (unsigned long long)OCFS2_I(inode)->ip_blkno,
- i_size_read(inode),
- (unsigned long long)le64_to_cpu(fe->i_size),
- le32_to_cpu(fe->i_flags));
+ if (unlikely(le64_to_cpu(fe->i_size) != i_size_read(inode))) {
+ status = ocfs2_error(inode->i_sb,
+ "Inode %llu has inconsistent i_size: inode = %lld, dinode = %llu, i_flags = 0x%x\n",
+ (unsigned long long)OCFS2_I(inode)->ip_blkno,
+ i_size_read(inode),
+ (unsigned long long)le64_to_cpu(fe->i_size),
+ le32_to_cpu(fe->i_flags));
+ goto bail;
+ }
if (new_i_size > le64_to_cpu(fe->i_size)) {
trace_ocfs2_truncate_file_error(
diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index a510a0eb1adc..180107a11046 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -13,6 +13,7 @@
#include <linux/pagemap.h>
#include <linux/quotaops.h>
#include <linux/iversion.h>
+#include <linux/fs_dirent.h>
#include <asm/byteorder.h>
@@ -64,7 +65,40 @@ static int ocfs2_filecheck_read_inode_block_full(struct inode *inode,
static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
struct buffer_head *bh);
static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
- struct buffer_head *bh);
+ struct buffer_head *bh);
+
+static bool ocfs2_valid_inode_mode(umode_t mode)
+{
+ return fs_umode_to_ftype(mode) != FT_UNKNOWN;
+}
+
+static bool ocfs2_dinode_has_unexpected_rdev(struct ocfs2_dinode *di)
+{
+ umode_t mode = le16_to_cpu(di->i_mode);
+
+ if (le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL)
+ return false;
+
+ return !S_ISCHR(mode) && !S_ISBLK(mode) && di->id1.dev1.i_rdev != 0;
+}
+
+static bool ocfs2_dinode_has_size_without_clusters(struct super_block *sb,
+ struct ocfs2_dinode *di)
+{
+ umode_t mode = le16_to_cpu(di->i_mode);
+
+ if (le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL)
+ return false;
+ if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL)
+ return false;
+ if (!le64_to_cpu(di->i_size) || le32_to_cpu(di->i_clusters))
+ return false;
+
+ if (S_ISDIR(mode))
+ return true;
+
+ return !ocfs2_sparse_alloc(OCFS2_SB(sb)) && S_ISREG(mode);
+}
void ocfs2_set_inode_flags(struct inode *inode)
{
@@ -1494,6 +1528,106 @@ int ocfs2_validate_inode_block(struct super_block *sb,
goto bail;
}
+ if ((le32_to_cpu(di->i_flags) & OCFS2_ORPHANED_FL) &&
+ le16_to_cpu(di->i_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
+ rc = ocfs2_error(sb, "Invalid dinode %llu: orphaned slot %u\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(di->i_orphaned_slot));
+ goto bail;
+ }
+
+ if ((le32_to_cpu(di->i_flags) & OCFS2_DIO_ORPHANED_FL) &&
+ le16_to_cpu(di->i_dio_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
+ rc = ocfs2_error(sb, "Invalid dinode %llu: DIO orphaned slot %u\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(di->i_dio_orphaned_slot));
+ goto bail;
+ }
+
+ /*
+ * Reject dinodes whose i_mode does not name one of the seven
+ * canonical POSIX file types. ocfs2_populate_inode() copies
+ * i_mode verbatim into inode->i_mode and then dispatches via
+ * switch (mode & S_IFMT) to file/dir/symlink/special_file iops;
+ * an unrecognised type falls into ocfs2_special_file_iops with
+ * init_special_inode(), which interprets i_rdev. Constrain the
+ * type here so the dispatch only ever sees a value mkfs.ocfs2 /
+ * VFS can produce.
+ */
+ if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: mode 0%o has unknown file type\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(di->i_mode));
+ goto bail;
+ }
+
+ /*
+ * id1.dev1.i_rdev is the device-number arm of the id1 union and
+ * is only meaningful for character and block device inodes. For
+ * any other regular user-visible file type the on-disk value
+ * must be zero. ocfs2_populate_inode() currently runs
+ *
+ * inode->i_rdev = huge_decode_dev(le64_to_cpu(fe->id1.dev1.i_rdev));
+ *
+ * unconditionally, before the S_IFMT switch decides whether the
+ * inode is a special file. As a result, an i_rdev value present
+ * on a non-device inode is silently published into the in-core
+ * inode; a subsequent forced re-read or in-core mode mutation
+ * (cluster peer with raw write access to the shared LUN,
+ * on-disk corruption, or a separately forged dinode) can then
+ * expose the attacker-controlled device number to
+ * init_special_inode() without ever showing an unusual i_mode
+ * at validation time.
+ *
+ * System inodes (OCFS2_SYSTEM_FL) legitimately use the bitmap1
+ * and journal1 arms of the same union (allocator i_used /
+ * i_total counters and the journal ij_flags /
+ * ij_recovery_generation pair); those bytes are not an i_rdev
+ * and must not be checked here. Restrict the cross-check to
+ * non-system inodes, which is the full attacker-controllable
+ * surface.
+ */
+ if (ocfs2_dinode_has_unexpected_rdev(di)) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: non-device mode 0%o with i_rdev %llu\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(di->i_mode),
+ (unsigned long long)le64_to_cpu(di->id1.dev1.i_rdev));
+ goto bail;
+ }
+
+ /*
+ * Non-inline directories must not have i_size without allocated
+ * clusters: directory growth adds storage before advancing i_size,
+ * and readdir walks i_size block-by-block. A forged directory
+ * with zero clusters and a huge i_size would repeatedly fault on
+ * holes while advancing through the claimed size.
+ *
+ * Non-inline regular files have the same invariant on non-sparse
+ * volumes. Sparse regular files are different: truncate can
+ * legitimately grow i_size without allocating clusters, so keep
+ * the sparse-alloc carveout for S_IFREG only. System inodes and
+ * inline-data dinodes have their own storage rules.
+ */
+ if (ocfs2_dinode_has_size_without_clusters(sb, di)) {
+ if (S_ISDIR(le16_to_cpu(di->i_mode)))
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: directory i_size %llu with i_clusters 0 and no inline-data flag\n",
+ (unsigned long long)bh->b_blocknr,
+ (unsigned long long)le64_to_cpu(di->i_size));
+ else
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: regular file i_size %llu with i_clusters 0 and no inline-data flag on non-sparse volume\n",
+ (unsigned long long)bh->b_blocknr,
+ (unsigned long long)le64_to_cpu(di->i_size));
+ goto bail;
+ }
+
+ rc = ocfs2_validate_inode_xattr(sb, bh->b_blocknr, di);
+ if (rc)
+ goto bail;
+
if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) {
struct ocfs2_inline_data *data = &di->id2.i_data;
@@ -1525,6 +1659,29 @@ int ocfs2_validate_inode_block(struct super_block *sb,
}
}
+ if (S_ISLNK(le16_to_cpu(di->i_mode)) &&
+ !le32_to_cpu(di->i_clusters)) {
+ int max_inline = ocfs2_fast_symlink_chars(sb);
+ u64 i_size = le64_to_cpu(di->i_size);
+
+ if (i_size >= max_inline) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: fast symlink i_size %llu exceeds max %d\n",
+ (unsigned long long)bh->b_blocknr,
+ (unsigned long long)i_size,
+ max_inline - 1);
+ goto bail;
+ }
+
+ if (strnlen((char *)di->id2.i_symlink, i_size + 1) != i_size) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: fast symlink is not NUL-terminated at i_size %llu\n",
+ (unsigned long long)bh->b_blocknr,
+ (unsigned long long)i_size);
+ goto bail;
+ }
+ }
+
if (le32_to_cpu(di->i_flags) & OCFS2_CHAIN_FL) {
struct ocfs2_chain_list *cl = &di->id2.i_chain;
u16 bpc = 1 << (OCFS2_SB(sb)->s_clustersize_bits -
@@ -1559,6 +1716,38 @@ int ocfs2_validate_inode_block(struct super_block *sb,
goto bail;
}
+ if (ocfs2_dinode_has_extents(di)) {
+ struct ocfs2_extent_list *el = &di->id2.i_list;
+ u16 count = le16_to_cpu(el->l_count);
+ u16 next_free = le16_to_cpu(el->l_next_free_rec);
+
+ if (count == 0) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode %llu: extent list l_count is zero\n",
+ (unsigned long long)bh->b_blocknr);
+ goto bail;
+ }
+ /*
+ * The exact capacity depends on i_xattr_inline_size, another
+ * unvalidated on-disk field. Inline xattrs only shrink the
+ * list, so the no-xattr maximum is a safe upper bound that a
+ * valid l_count never exceeds.
+ */
+ if (count > ocfs2_extent_recs_per_inode(sb)) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode %llu: extent list l_count %u exceeds max %u\n",
+ (unsigned long long)bh->b_blocknr, count,
+ ocfs2_extent_recs_per_inode(sb));
+ goto bail;
+ }
+ if (next_free > count) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode %llu: extent list l_next_free_rec %u exceeds l_count %u\n",
+ (unsigned long long)bh->b_blocknr, next_free, count);
+ goto bail;
+ }
+ }
+
rc = 0;
bail:
@@ -1624,6 +1813,40 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
(unsigned long long)bh->b_blocknr,
le32_to_cpu(di->i_fs_generation));
rc = -OCFS2_FILECHECK_ERR_GENERATION;
+ goto bail;
+ }
+
+ if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) {
+ mlog(ML_ERROR,
+ "Filecheck: invalid dinode #%llu: mode 0%o has unknown file type\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(di->i_mode));
+ rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+ goto bail;
+ }
+
+ if (ocfs2_dinode_has_unexpected_rdev(di)) {
+ mlog(ML_ERROR,
+ "Filecheck: invalid dinode #%llu: non-device mode 0%o with i_rdev %llu\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(di->i_mode),
+ (unsigned long long)le64_to_cpu(di->id1.dev1.i_rdev));
+ rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+ goto bail;
+ }
+
+ if (ocfs2_dinode_has_size_without_clusters(sb, di)) {
+ if (S_ISDIR(le16_to_cpu(di->i_mode)))
+ mlog(ML_ERROR,
+ "Filecheck: invalid dinode #%llu: directory i_size %llu with i_clusters 0 and no inline-data flag\n",
+ (unsigned long long)bh->b_blocknr,
+ (unsigned long long)le64_to_cpu(di->i_size));
+ else
+ mlog(ML_ERROR,
+ "Filecheck: invalid dinode #%llu: regular file i_size %llu with i_clusters 0 and no inline-data flag on non-sparse volume\n",
+ (unsigned long long)bh->b_blocknr,
+ (unsigned long long)le64_to_cpu(di->i_size));
+ rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
}
bail:
@@ -1745,8 +1968,6 @@ int ocfs2_read_inode_block_full(struct inode *inode, struct buffer_head **bh,
rc = ocfs2_read_blocks(INODE_CACHE(inode), OCFS2_I(inode)->ip_blkno,
1, &tmp, flags, ocfs2_validate_inode_block);
- if (rc < 0)
- make_bad_inode(inode);
/* If ocfs2_read_blocks() got us a new bh, pass it up. */
if (!rc && !*bh)
*bh = tmp;
@@ -1812,4 +2033,3 @@ const struct ocfs2_caching_operations ocfs2_inode_caching_ops = {
.co_io_lock = ocfs2_inode_cache_io_lock,
.co_io_unlock = ocfs2_inode_cache_io_unlock,
};
-
diff --git a/fs/ocfs2/journal.c b/fs/ocfs2/journal.c
index f9bf3bac085d..d8afbc1a76bb 100644
--- a/fs/ocfs2/journal.c
+++ b/fs/ocfs2/journal.c
@@ -473,8 +473,12 @@ bail:
*/
int ocfs2_assure_trans_credits(handle_t *handle, int nblocks)
{
- int old_nblks = jbd2_handle_buffer_credits(handle);
+ int old_nblks;
+ if (is_handle_aborted(handle))
+ return -EROFS;
+
+ old_nblks = jbd2_handle_buffer_credits(handle);
trace_ocfs2_assure_trans_credits(old_nblks);
if (old_nblks >= nblocks)
return 0;
@@ -1022,11 +1026,8 @@ static int ocfs2_journal_toggle_dirty(struct ocfs2_super *osb,
struct ocfs2_dinode *fe;
fe = (struct ocfs2_dinode *)bh->b_data;
-
- /* The journal bh on the osb always comes from ocfs2_journal_init()
- * and was validated there inside ocfs2_inode_lock_full(). It's a
- * code bug if we mess it up. */
- BUG_ON(!OCFS2_IS_VALID_DINODE(fe));
+ if (WARN_ON(!OCFS2_IS_VALID_DINODE(fe)))
+ return -EIO;
flags = le32_to_cpu(fe->id1.journal1.ij_flags);
if (dirty)
diff --git a/fs/ocfs2/journal.h b/fs/ocfs2/journal.h
index 6397170f302f..f8b3b2a3d630 100644
--- a/fs/ocfs2/journal.h
+++ b/fs/ocfs2/journal.h
@@ -196,6 +196,9 @@ static inline void ocfs2_checkpoint_inode(struct inode *inode)
if (ocfs2_mount_local(osb))
return;
+ if (!osb->journal)
+ return;
+
if (!ocfs2_ci_fully_checkpointed(INODE_CACHE(inode))) {
/* WARNING: This only kicks off a single
* checkpoint. If someone races you and adds more
diff --git a/fs/ocfs2/move_extents.c b/fs/ocfs2/move_extents.c
index c53de4439d93..ad1678ee7cc4 100644
--- a/fs/ocfs2/move_extents.c
+++ b/fs/ocfs2/move_extents.c
@@ -534,6 +534,8 @@ static void ocfs2_probe_alloc_group(struct inode *inode, struct buffer_head *bh,
u32 base_cpos = ocfs2_blocks_to_clusters(inode->i_sb,
le64_to_cpu(gd->bg_blkno));
+ *phys_cpos = 0;
+
for (i = base_bit; i < le16_to_cpu(gd->bg_bits); i++) {
used = ocfs2_test_bit(i, (unsigned long *)gd->bg_bitmap);
@@ -555,7 +557,7 @@ static void ocfs2_probe_alloc_group(struct inode *inode, struct buffer_head *bh,
last_free_bits++;
if (last_free_bits == move_len) {
- i -= move_len;
+ i = i - move_len + 1;
*goal_bit = i;
*phys_cpos = base_cpos + i;
break;
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 1277666c77cd..58c6061ed983 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -256,6 +256,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
sigset_t oldset;
int did_block_signals = 0;
struct ocfs2_dentry_lock *dl = NULL;
+ struct ocfs2_acl_state acl_state = { 0 };
trace_ocfs2_mknod(dir, dentry, dentry->d_name.len, dentry->d_name.name,
(unsigned long long)OCFS2_I(dir)->ip_blkno,
@@ -330,14 +331,13 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
}
}
- /* calculate meta data/clusters for setting security and acl xattr */
- status = ocfs2_calc_xattr_init(dir, parent_fe_bh, mode,
- &si, &want_clusters,
- &xattr_credits, &want_meta);
- if (status < 0) {
- mlog_errno(status);
+ status = ocfs2_acl_init_prepare(inode, dir, parent_fe_bh, &acl_state);
+ if (status < 0)
goto leave;
- }
+
+ /* calculate meta data/clusters for setting security and acl xattr */
+ ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, &xattr_credits,
+ &want_meta, &acl_state);
/* Reserve a cluster if creating an extent based directory. */
if (S_ISDIR(mode) && !ocfs2_supports_inline_data(osb)) {
@@ -411,8 +411,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
inc_nlink(dir);
}
- status = ocfs2_init_acl(handle, inode, dir, new_fe_bh, parent_fe_bh,
- meta_ac, data_ac);
+ status = ocfs2_init_acl(handle, inode, new_fe_bh, meta_ac, data_ac,
+ &acl_state);
if (status < 0) {
mlog_errno(status);
@@ -477,6 +477,8 @@ leave:
brelse(parent_fe_bh);
kfree(si.value);
+ ocfs2_acl_init_release(&acl_state);
+
ocfs2_free_dir_lookup_result(&lookup);
if (inode_ac)
@@ -657,7 +659,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap,
trace_ocfs2_mkdir(dir, dentry, dentry->d_name.len, dentry->d_name.name,
OCFS2_I(dir)->ip_blkno, mode);
- ret = ocfs2_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0);
+ ret = ocfs2_mknod(&nop_mnt_idmap, dir, dentry, mode, 0);
if (ret)
mlog_errno(ret);
@@ -667,8 +669,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap,
static int ocfs2_create(struct mnt_idmap *idmap,
struct inode *dir,
struct dentry *dentry,
- umode_t mode,
- bool excl)
+ umode_t mode)
{
int ret;
@@ -945,7 +946,10 @@ static int ocfs2_unlink(struct inode *dir,
child_locked = 1;
if (S_ISDIR(inode->i_mode)) {
- if (inode->i_nlink != 2 || !ocfs2_empty_dir(inode)) {
+ status = ocfs2_empty_dir(inode);
+ if (status < 0)
+ goto leave;
+ if (inode->i_nlink != 2 || !status) {
status = -ENOTEMPTY;
goto leave;
}
@@ -1499,8 +1503,10 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
if (target_exists) {
if (S_ISDIR(new_inode->i_mode)) {
- if (new_inode->i_nlink != 2 ||
- !ocfs2_empty_dir(new_inode)) {
+ status = ocfs2_empty_dir(new_inode);
+ if (status < 0)
+ goto bail;
+ if (new_inode->i_nlink != 2 || !status) {
status = -ENOTEMPTY;
goto bail;
}
@@ -2126,7 +2132,7 @@ static int ocfs2_lookup_lock_orphan_dir(struct ocfs2_super *osb,
return ret;
}
- inode_lock(orphan_dir_inode);
+ inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2);
ret = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1);
if (ret < 0) {
@@ -2725,7 +2731,7 @@ int ocfs2_del_inode_from_orphan(struct ocfs2_super *osb,
goto bail;
}
- inode_lock(orphan_dir_inode);
+ inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2);
status = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1);
if (status < 0) {
inode_unlock(orphan_dir_inode);
@@ -2838,7 +2844,7 @@ int ocfs2_mv_orphaned_inode_to_new(struct inode *dir,
goto leave;
}
- inode_lock(orphan_dir_inode);
+ inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2);
status = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1);
if (status < 0) {
diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
index 7b50e03dfa66..62cad6522c7a 100644
--- a/fs/ocfs2/ocfs2.h
+++ b/fs/ocfs2/ocfs2.h
@@ -494,8 +494,6 @@ struct ocfs2_super
struct rb_root osb_rf_lock_tree;
struct ocfs2_refcount_tree *osb_ref_tree_lru;
- struct mutex system_file_mutex;
-
/*
* OCFS2 needs to schedule several different types of work which
* require cluster locking, disk I/O, recovery waits, etc. Since these
diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c
index 12cbb4fccda0..f55810c59b1b 100644
--- a/fs/ocfs2/quota_local.c
+++ b/fs/ocfs2/quota_local.c
@@ -302,7 +302,7 @@ static int ocfs2_add_recovery_chunk(struct super_block *sb,
if (!rc)
return -ENOMEM;
rc->rc_chunk = chunk;
- rc->rc_bitmap = kmalloc(sb->s_blocksize, GFP_NOFS);
+ rc->rc_bitmap = kzalloc(sb->s_blocksize, GFP_NOFS);
if (!rc->rc_bitmap) {
kfree(rc);
return -ENOMEM;
diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c
index 8eee5be4d1ed..d9f22b4a2654 100644
--- a/fs/ocfs2/refcounttree.c
+++ b/fs/ocfs2/refcounttree.c
@@ -116,6 +116,33 @@ static int ocfs2_validate_refcount_block(struct super_block *sb,
le32_to_cpu(rb->rf_fs_generation));
goto out;
}
+
+ /*
+ * rf_records (rl_count/rl_used/rl_recs[]) is only meaningful when
+ * this block is not an interior tree block (OCFS2_REFCOUNT_TREE_FL);
+ * in that case the same union bytes hold an extent list (rf_list)
+ * instead, which is validated by ocfs2_validate_extent_block().
+ */
+ if (!(le32_to_cpu(rb->rf_flags) & OCFS2_REFCOUNT_TREE_FL)) {
+ if (le16_to_cpu(rb->rf_records.rl_count) !=
+ ocfs2_refcount_recs_per_rb(sb)) {
+ rc = ocfs2_error(sb,
+ "Refcount block #%llu has an invalid rl_count of %u\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(rb->rf_records.rl_count));
+ goto out;
+ }
+
+ if (le16_to_cpu(rb->rf_records.rl_used) >
+ le16_to_cpu(rb->rf_records.rl_count)) {
+ rc = ocfs2_error(sb,
+ "Refcount block #%llu has an invalid rl_used of %u (rl_count %u)\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(rb->rf_records.rl_used),
+ le16_to_cpu(rb->rf_records.rl_count));
+ goto out;
+ }
+ }
out:
return rc;
}
@@ -2131,10 +2158,15 @@ static int ocfs2_remove_refcount_extent(handle_t *handle,
rb->rf_flags = 0;
rb->rf_parent = 0;
rb->rf_cpos = 0;
- memset(&rb->rf_records, 0, sb->s_blocksize -
- offsetof(struct ocfs2_refcount_block, rf_records));
+ rb->rf_records.rl_used = 0;
+ rb->rf_records.rl_reserved2 = 0;
+ rb->rf_records.rl_reserved1 = 0;
+ /* rl_count determines the memset size and fortify object size. */
rb->rf_records.rl_count =
cpu_to_le16(ocfs2_refcount_recs_per_rb(sb));
+ memset(rb->rf_records.rl_recs, 0,
+ le16_to_cpu(rb->rf_records.rl_count) *
+ sizeof(*rb->rf_records.rl_recs));
}
ocfs2_journal_dirty(handle, ref_root_bh);
@@ -3355,10 +3387,9 @@ static int ocfs2_replace_cow(struct ocfs2_cow_context *context)
cow_start += num_clusters;
}
- if (ocfs2_dealloc_has_cluster(&context->dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&context->dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &context->dealloc);
- }
+ ocfs2_run_deallocs(osb, &context->dealloc);
return ret;
}
@@ -3841,10 +3872,9 @@ unlock:
ocfs2_unlock_refcount_tree(osb, ref_tree, 1);
brelse(ref_root_bh);
- if (!ret && ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (!ret && ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &dealloc);
- }
+ ocfs2_run_deallocs(osb, &dealloc);
out:
/*
* Empty the extent map so that we may get the right extent
@@ -4130,10 +4160,9 @@ out_unlock_refcount:
ocfs2_unlock_refcount_tree(osb, ref_tree, 1);
brelse(ref_root_bh);
out:
- if (ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &dealloc);
- }
+ ocfs2_run_deallocs(osb, &dealloc);
return ret;
}
@@ -4686,10 +4715,9 @@ loff_t ocfs2_reflink_remap_blocks(struct inode *s_inode,
}
out:
- if (ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &dealloc);
- }
+ ocfs2_run_deallocs(osb, &dealloc);
return ret;
}
diff --git a/fs/ocfs2/stack_user.c b/fs/ocfs2/stack_user.c
index 5803f1dee679..91e19d33847c 100644
--- a/fs/ocfs2/stack_user.c
+++ b/fs/ocfs2/stack_user.c
@@ -327,18 +327,14 @@ static int ocfs2_control_install_private(struct file *file)
ocfs2_control_this_node = p->op_this_node;
running_proto.pv_major = p->op_proto.pv_major;
running_proto.pv_minor = p->op_proto.pv_minor;
- }
-
-out_unlock:
- mutex_unlock(&ocfs2_control_lock);
-
- if (!rc && set_p) {
- /* We set the global values successfully */
atomic_inc(&ocfs2_control_opened);
ocfs2_control_set_handshake_state(file,
OCFS2_CONTROL_HANDSHAKE_VALID);
}
+out_unlock:
+ mutex_unlock(&ocfs2_control_lock);
+
return rc;
}
diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c
index d284e0e37252..20c3aec6b987 100644
--- a/fs/ocfs2/suballoc.c
+++ b/fs/ocfs2/suballoc.c
@@ -231,8 +231,16 @@ static int ocfs2_validate_gd_parent(struct super_block *sb,
int resize)
{
unsigned int max_bits;
+ unsigned int max_bitmap_bits;
+ unsigned int max_bitmap_size;
+ int suballocator;
struct ocfs2_group_desc *gd = (struct ocfs2_group_desc *)bh->b_data;
+ suballocator = le64_to_cpu(di->i_blkno) != OCFS2_SB(sb)->bitmap_blkno;
+ max_bitmap_size = ocfs2_group_bitmap_size(sb, suballocator,
+ OCFS2_SB(sb)->s_feature_incompat);
+ max_bitmap_bits = max_bitmap_size * 8;
+
if (di->i_blkno != gd->bg_parent_dinode) {
do_error("Group descriptor #%llu has bad parent pointer (%llu, expected %llu)\n",
(unsigned long long)bh->b_blocknr,
@@ -240,6 +248,20 @@ static int ocfs2_validate_gd_parent(struct super_block *sb,
(unsigned long long)le64_to_cpu(di->i_blkno));
}
+ if (le16_to_cpu(gd->bg_size) > max_bitmap_size) {
+ do_error("Group descriptor #%llu has bitmap size %u but physical max of %u\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(gd->bg_size),
+ max_bitmap_size);
+ }
+
+ if (le16_to_cpu(gd->bg_bits) > max_bitmap_bits) {
+ do_error("Group descriptor #%llu has bit count %u but physical max of %u\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(gd->bg_bits),
+ max_bitmap_bits);
+ }
+
max_bits = le16_to_cpu(di->id2.i_chain.cl_cpg) * le16_to_cpu(di->id2.i_chain.cl_bpc);
if (le16_to_cpu(gd->bg_bits) > max_bits) {
do_error("Group descriptor #%llu has bit count of %u\n",
@@ -2737,7 +2759,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle,
fe->i_clusters = cpu_to_le32(tmp_used - le16_to_cpu(cl->cl_cpg));
spin_lock(&OCFS2_I(alloc_inode)->ip_lock);
- OCFS2_I(alloc_inode)->ip_clusters -= le32_to_cpu(fe->i_clusters);
+ OCFS2_I(alloc_inode)->ip_clusters = le32_to_cpu(fe->i_clusters);
fe->i_size = cpu_to_le64(ocfs2_clusters_to_bytes(alloc_inode->i_sb,
le32_to_cpu(fe->i_clusters)));
spin_unlock(&OCFS2_I(alloc_inode)->ip_lock);
diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c
index b875f01c9756..c62e389d4dd6 100644
--- a/fs/ocfs2/super.c
+++ b/fs/ocfs2/super.c
@@ -1224,7 +1224,6 @@ static struct file_system_type ocfs2_fs_type = {
.name = "ocfs2",
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV|FS_RENAME_DOES_D_MOVE,
- .next = NULL,
.init_fs_context = ocfs2_init_fs_context,
.parameters = ocfs2_param_spec,
};
@@ -1883,7 +1882,6 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err)
ocfs2_delete_osb(osb);
kfree(osb);
- sb->s_dev = 0;
sb->s_fs_info = NULL;
}
@@ -1997,8 +1995,6 @@ static int ocfs2_initialize_super(struct super_block *sb,
spin_lock_init(&osb->osb_xattr_lock);
ocfs2_init_steal_slots(osb);
- mutex_init(&osb->system_file_mutex);
-
atomic_set(&osb->alloc_stats.moves, 0);
atomic_set(&osb->alloc_stats.local_data, 0);
atomic_set(&osb->alloc_stats.bitmap_data, 0);
diff --git a/fs/ocfs2/sysfile.c b/fs/ocfs2/sysfile.c
index d53a6cc866be..67e492f4b828 100644
--- a/fs/ocfs2/sysfile.c
+++ b/fs/ocfs2/sysfile.c
@@ -98,11 +98,9 @@ struct inode *ocfs2_get_system_file_inode(struct ocfs2_super *osb,
} else
arr = get_local_system_inode(osb, type, slot);
- mutex_lock(&osb->system_file_mutex);
if (arr && ((inode = *arr) != NULL)) {
/* get a ref in addition to the array ref */
inode = igrab(inode);
- mutex_unlock(&osb->system_file_mutex);
BUG_ON(!inode);
return inode;
@@ -112,11 +110,10 @@ struct inode *ocfs2_get_system_file_inode(struct ocfs2_super *osb,
inode = _ocfs2_get_system_file_inode(osb, type, slot);
/* add one more if putting into array for first time */
- if (arr && inode) {
- *arr = igrab(inode);
- BUG_ON(!*arr);
+ if (inode && arr && !*arr && !cmpxchg(&(*arr), NULL, inode)) {
+ inode = igrab(inode);
+ BUG_ON(!inode);
}
- mutex_unlock(&osb->system_file_mutex);
return inode;
}
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 86cfd4c2adf9..bfafe059bedf 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -390,6 +390,12 @@ static int ocfs2_init_xattr_bucket(struct ocfs2_xattr_bucket *bucket,
return rc;
}
+static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno,
+ struct ocfs2_xattr_header *xh,
+ size_t region_size);
+static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket,
+ u64 blkno);
+
/* Read the xattr bucket at xb_blkno */
static int ocfs2_read_xattr_bucket(struct ocfs2_xattr_bucket *bucket,
u64 xb_blkno)
@@ -408,6 +414,8 @@ static int ocfs2_read_xattr_bucket(struct ocfs2_xattr_bucket *bucket,
spin_unlock(&OCFS2_SB(bucket->bu_inode->i_sb)->osb_xattr_lock);
if (rc)
mlog_errno(rc);
+ else
+ rc = ocfs2_validate_xattr_bucket(bucket, xb_blkno);
}
if (rc)
@@ -509,6 +517,22 @@ static int ocfs2_validate_xattr_block(struct super_block *sb,
le32_to_cpu(xb->xb_fs_generation));
}
+ if (!(le16_to_cpu(xb->xb_flags) & OCFS2_XATTR_INDEXED)) {
+ size_t region_offset =
+ offsetof(struct ocfs2_xattr_block, xb_attrs.xb_header);
+
+ if (bh->b_size < region_offset)
+ return ocfs2_error(sb,
+ "Invalid xattr block %llu: block size %zu is too small\n",
+ (unsigned long long)bh->b_blocknr,
+ bh->b_size);
+
+ return ocfs2_validate_xattr_entries_flat(sb, bh->b_blocknr,
+ &xb->xb_attrs.xb_header,
+ bh->b_size -
+ region_offset);
+ }
+
return 0;
}
@@ -611,15 +635,11 @@ int ocfs2_calc_security_init(struct inode *dir,
return ret;
}
-int ocfs2_calc_xattr_init(struct inode *dir,
- struct buffer_head *dir_bh,
- umode_t mode,
- struct ocfs2_security_xattr_info *si,
- int *want_clusters,
- int *xattr_credits,
- int *want_meta)
+void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
+ struct ocfs2_security_xattr_info *si,
+ int *want_clusters, int *xattr_credits,
+ int *want_meta, struct ocfs2_acl_state *acl_state)
{
- int ret = 0;
struct ocfs2_super *osb = OCFS2_SB(dir->i_sb);
int s_size = 0, a_size = 0, acl_len = 0, new_clusters;
@@ -628,24 +648,20 @@ int ocfs2_calc_xattr_init(struct inode *dir,
si->value_len);
if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
- down_read(&OCFS2_I(dir)->ip_xattr_sem);
- acl_len = ocfs2_xattr_get_nolock(dir, dir_bh,
- OCFS2_XATTR_INDEX_POSIX_ACL_DEFAULT,
- "", NULL, 0);
- up_read(&OCFS2_I(dir)->ip_xattr_sem);
- if (acl_len > 0) {
- a_size = ocfs2_xattr_entry_real_size(0, acl_len);
- if (S_ISDIR(mode))
- a_size <<= 1;
- } else if (acl_len != 0 && acl_len != -ENODATA) {
- ret = acl_len;
- mlog_errno(ret);
- return ret;
+ if (acl_state->default_acl && S_ISDIR(mode)) {
+ acl_len = acl_state->default_acl->a_count *
+ sizeof(struct ocfs2_acl_entry);
+ a_size += ocfs2_xattr_entry_real_size(0, acl_len);
+ }
+ if (acl_state->acl) {
+ acl_len = acl_state->acl->a_count *
+ sizeof(struct ocfs2_acl_entry);
+ a_size += ocfs2_xattr_entry_real_size(0, acl_len);
}
}
if (!(s_size + a_size))
- return ret;
+ return;
/*
* The max space of security xattr taken inline is
@@ -683,17 +699,34 @@ int ocfs2_calc_xattr_init(struct inode *dir,
new_clusters);
*want_clusters += new_clusters;
}
- if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL &&
- acl_len > OCFS2_XATTR_INLINE_SIZE) {
- /* for directory, it has DEFAULT and ACCESS two types of acls */
- new_clusters = (S_ISDIR(mode) ? 2 : 1) *
- ocfs2_clusters_for_bytes(dir->i_sb, acl_len);
- *xattr_credits += ocfs2_clusters_to_blocks(dir->i_sb,
- new_clusters);
- *want_clusters += new_clusters;
+ if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) {
+ if (acl_state->default_acl && S_ISDIR(mode)) {
+ acl_len = acl_state->default_acl->a_count *
+ sizeof(struct ocfs2_acl_entry);
+ if (acl_len > OCFS2_XATTR_INLINE_SIZE) {
+ new_clusters =
+ ocfs2_clusters_for_bytes(dir->i_sb,
+ acl_len);
+ *xattr_credits +=
+ ocfs2_clusters_to_blocks(dir->i_sb,
+ new_clusters);
+ *want_clusters += new_clusters;
+ }
+ }
+ if (acl_state->acl) {
+ acl_len = acl_state->acl->a_count *
+ sizeof(struct ocfs2_acl_entry);
+ if (acl_len > OCFS2_XATTR_INLINE_SIZE) {
+ new_clusters =
+ ocfs2_clusters_for_bytes(dir->i_sb,
+ acl_len);
+ *xattr_credits +=
+ ocfs2_clusters_to_blocks(dir->i_sb,
+ new_clusters);
+ *want_clusters += new_clusters;
+ }
+ }
}
-
- return ret;
}
static int ocfs2_xattr_extend_allocation(struct inode *inode,
@@ -740,12 +773,10 @@ static int ocfs2_xattr_extend_allocation(struct inode *inode,
prev_clusters;
if (why != RESTART_NONE && clusters_to_add) {
- /*
- * We can only fail in case the alloc file doesn't give
- * up enough clusters.
- */
- BUG_ON(why == RESTART_META);
-
+ if (why == RESTART_META) {
+ status = -ENOSPC;
+ break;
+ }
credits = ocfs2_calc_extend_credits(inode->i_sb,
&vb->vb_xv->xr_list);
status = ocfs2_extend_trans(handle, credits);
@@ -950,15 +981,216 @@ static int ocfs2_xattr_list_entries(struct inode *inode,
return result;
}
+static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno,
+ struct ocfs2_xattr_header *xh,
+ size_t region_size)
+{
+ u16 xattr_count = le16_to_cpu(xh->xh_count);
+ size_t entries_limit = region_size;
+ size_t nv_limit = region_size;
+ size_t max_entries;
+ int i;
+
+ if (region_size < sizeof(*xh))
+ return ocfs2_error(sb,
+ "Invalid xattr in block %llu: region size %zu is too small\n",
+ (unsigned long long)blkno, region_size);
+
+ max_entries = (entries_limit - sizeof(*xh)) /
+ sizeof(struct ocfs2_xattr_entry);
+
+ if (xattr_count > max_entries)
+ return ocfs2_error(sb,
+ "Invalid xattr in block %llu: entry count %u exceeds maximum %zu\n",
+ (unsigned long long)blkno,
+ xattr_count, max_entries);
+
+ for (i = 0; i < xattr_count; i++) {
+ struct ocfs2_xattr_entry *xe = &xh->xh_entries[i];
+ size_t name_offset = le16_to_cpu(xe->xe_name_offset);
+ size_t value_offset;
+
+ if (name_offset > nv_limit ||
+ xe->xe_name_len > nv_limit - name_offset)
+ return ocfs2_error(sb,
+ "Invalid xattr in block %llu: entry %d name is out of bounds\n",
+ (unsigned long long)blkno, i);
+
+ value_offset = name_offset + OCFS2_XATTR_SIZE(xe->xe_name_len);
+ if (value_offset > nv_limit)
+ return ocfs2_error(sb,
+ "Invalid xattr in block %llu: entry %d value starts out of bounds\n",
+ (unsigned long long)blkno, i);
+
+ if (ocfs2_xattr_is_local(xe)) {
+ if (le64_to_cpu(xe->xe_value_size) >
+ nv_limit - value_offset)
+ return ocfs2_error(sb,
+ "Invalid xattr in block %llu: entry %d value is out of bounds\n",
+ (unsigned long long)blkno,
+ i);
+ } else if (sizeof(struct ocfs2_xattr_value_root) >
+ nv_limit - value_offset) {
+ return ocfs2_error(sb,
+ "Invalid xattr in block %llu: entry %d value root is out of bounds\n",
+ (unsigned long long)blkno, i);
+ }
+ }
+
+ return 0;
+}
+
+static int ocfs2_xattr_ibody_lookup_header_raw(struct super_block *sb,
+ u64 blkno,
+ struct ocfs2_dinode *di,
+ struct ocfs2_xattr_header **header,
+ u16 *inline_size_ret)
+{
+ struct ocfs2_xattr_header *xh;
+ u16 xattr_count;
+ size_t max_entries;
+ u16 inline_size = le16_to_cpu(di->i_xattr_inline_size);
+
+ if (inline_size > sb->s_blocksize ||
+ inline_size < sizeof(struct ocfs2_xattr_header)) {
+ ocfs2_error(sb,
+ "Invalid inode %llu: xattr inline size %u\n",
+ (unsigned long long)blkno, inline_size);
+ return -EFSCORRUPTED;
+ }
+
+ xh = (struct ocfs2_xattr_header *)
+ ((void *)di + sb->s_blocksize - inline_size);
+
+ xattr_count = le16_to_cpu(xh->xh_count);
+ max_entries = (inline_size - sizeof(struct ocfs2_xattr_header)) /
+ sizeof(struct ocfs2_xattr_entry);
+
+ if (xattr_count > max_entries) {
+ ocfs2_error(sb,
+ "xattr entry count %u exceeds maximum %zu in inode %llu\n",
+ xattr_count, max_entries,
+ (unsigned long long)blkno);
+ return -EFSCORRUPTED;
+ }
+
+ *header = xh;
+ if (inline_size_ret)
+ *inline_size_ret = inline_size;
+
+ return 0;
+}
+
+int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno,
+ struct ocfs2_dinode *di)
+{
+ struct ocfs2_xattr_header *xh;
+ u16 inline_size;
+ int ret;
+
+ if (!(le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_XATTR_FL))
+ return 0;
+
+ ret = ocfs2_xattr_ibody_lookup_header_raw(sb, blkno, di, &xh,
+ &inline_size);
+ if (ret)
+ return ret;
+
+ return ocfs2_validate_xattr_entries_flat(sb, blkno, xh, inline_size);
+}
+
+static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket,
+ u64 blkno)
+{
+ struct super_block *sb = bucket->bu_inode->i_sb;
+ struct ocfs2_xattr_header *xh = bucket_xh(bucket);
+ u16 xattr_count = le16_to_cpu(xh->xh_count);
+ size_t region_size = (size_t)sb->s_blocksize * bucket->bu_blocks;
+ size_t entries_limit = sb->s_blocksize;
+ size_t nv_limit = sb->s_blocksize;
+ size_t max_entries;
+ int i;
+
+ if (region_size < sizeof(*xh))
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: region size %zu is too small\n",
+ (unsigned long long)blkno, region_size);
+
+ if (entries_limit < sizeof(*xh))
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entries limit %zu is too small\n",
+ (unsigned long long)blkno,
+ entries_limit);
+
+ max_entries = (entries_limit - sizeof(*xh)) /
+ sizeof(struct ocfs2_xattr_entry);
+
+ if (xattr_count > max_entries)
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entry count %u exceeds maximum %zu\n",
+ (unsigned long long)blkno,
+ xattr_count, max_entries);
+
+ for (i = 0; i < xattr_count; i++) {
+ struct ocfs2_xattr_entry *xe = &xh->xh_entries[i];
+ size_t name_offset = le16_to_cpu(xe->xe_name_offset);
+ size_t block_off = name_offset >> sb->s_blocksize_bits;
+ size_t block_offset = name_offset % nv_limit;
+ size_t value_offset;
+
+ if (name_offset >= region_size || block_off >= bucket->bu_blocks)
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entry %d name is out of bounds\n",
+ (unsigned long long)blkno, i);
+
+ if (xe->xe_name_len > nv_limit - block_offset)
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entry %d name crosses block boundary\n",
+ (unsigned long long)blkno, i);
+
+ value_offset = block_offset + OCFS2_XATTR_SIZE(xe->xe_name_len);
+ if (value_offset > nv_limit)
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entry %d value starts out of bounds\n",
+ (unsigned long long)blkno, i);
+
+ if (ocfs2_xattr_is_local(xe)) {
+ if (le64_to_cpu(xe->xe_value_size) >
+ nv_limit - value_offset)
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entry %d value is out of bounds\n",
+ (unsigned long long)blkno,
+ i);
+ } else if (sizeof(struct ocfs2_xattr_value_root) >
+ nv_limit - value_offset) {
+ return ocfs2_error(sb,
+ "Invalid xattr bucket %llu: entry %d value root is out of bounds\n",
+ (unsigned long long)blkno, i);
+ }
+ }
+
+ return 0;
+}
+
+static int ocfs2_xattr_ibody_lookup_header(struct inode *inode,
+ struct ocfs2_dinode *di,
+ struct ocfs2_xattr_header **header)
+{
+ return ocfs2_xattr_ibody_lookup_header_raw(inode->i_sb,
+ OCFS2_I(inode)->ip_blkno,
+ di, header, NULL);
+}
+
int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
struct ocfs2_dinode *di)
{
struct ocfs2_xattr_header *xh;
+ int ret;
int i;
- xh = (struct ocfs2_xattr_header *)
- ((void *)di + inode->i_sb->s_blocksize -
- le16_to_cpu(di->i_xattr_inline_size));
+ ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xh);
+ if (ret)
+ return 1;
for (i = 0; i < le16_to_cpu(xh->xh_count); i++)
if (!ocfs2_xattr_is_local(&xh->xh_entries[i]))
@@ -975,39 +1207,13 @@ static int ocfs2_xattr_ibody_list(struct inode *inode,
struct ocfs2_xattr_header *header = NULL;
struct ocfs2_inode_info *oi = OCFS2_I(inode);
int ret = 0;
- u16 xattr_count;
- size_t max_entries;
- u16 inline_size;
if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL))
return ret;
- inline_size = le16_to_cpu(di->i_xattr_inline_size);
-
- /* Validate inline size is reasonable */
- if (inline_size > inode->i_sb->s_blocksize ||
- inline_size < sizeof(struct ocfs2_xattr_header)) {
- ocfs2_error(inode->i_sb,
- "Invalid xattr inline size %u in inode %llu\n",
- inline_size,
- (unsigned long long)OCFS2_I(inode)->ip_blkno);
- return -EFSCORRUPTED;
- }
-
- header = (struct ocfs2_xattr_header *)
- ((void *)di + inode->i_sb->s_blocksize - inline_size);
-
- xattr_count = le16_to_cpu(header->xh_count);
- max_entries = (inline_size - sizeof(struct ocfs2_xattr_header)) /
- sizeof(struct ocfs2_xattr_entry);
-
- if (xattr_count > max_entries) {
- ocfs2_error(inode->i_sb,
- "xattr entry count %u exceeds maximum %zu in inode %llu\n",
- xattr_count, max_entries,
- (unsigned long long)OCFS2_I(inode)->ip_blkno);
- return -EFSCORRUPTED;
- }
+ ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
+ if (ret)
+ return ret;
ret = ocfs2_xattr_list_entries(inode, header, buffer, buffer_size);
@@ -1200,8 +1406,9 @@ static int ocfs2_xattr_ibody_get(struct inode *inode,
return -ENODATA;
xs->end = (void *)di + inode->i_sb->s_blocksize;
- xs->header = (struct ocfs2_xattr_header *)
- (xs->end - le16_to_cpu(di->i_xattr_inline_size));
+ ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xs->header);
+ if (ret)
+ return ret;
xs->base = (void *)xs->header;
xs->here = xs->header->xh_entries;
@@ -2465,9 +2672,9 @@ static int ocfs2_xattr_ibody_remove(struct inode *inode,
.vb_access = ocfs2_journal_access_di,
};
- header = (struct ocfs2_xattr_header *)
- ((void *)di + inode->i_sb->s_blocksize -
- le16_to_cpu(di->i_xattr_inline_size));
+ ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
+ if (ret)
+ return ret;
ret = ocfs2_remove_value_outside(inode, &vb, header,
ref_ci, ref_root_bh);
@@ -2726,12 +2933,14 @@ static int ocfs2_xattr_ibody_find(struct inode *inode,
xs->xattr_bh = xs->inode_bh;
xs->end = (void *)di + inode->i_sb->s_blocksize;
- if (oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL)
- xs->header = (struct ocfs2_xattr_header *)
- (xs->end - le16_to_cpu(di->i_xattr_inline_size));
- else
+ if (oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL) {
+ ret = ocfs2_xattr_ibody_lookup_header(inode, di, &xs->header);
+ if (ret)
+ return ret;
+ } else {
xs->header = (struct ocfs2_xattr_header *)
(xs->end - OCFS2_SB(inode->i_sb)->s_xattr_inline_size);
+ }
xs->base = (void *)xs->header;
xs->here = xs->header->xh_entries;
@@ -3242,6 +3451,14 @@ meta_guess:
credits += OCFS2_SUBALLOC_ALLOC + 1;
/*
+ * Reserve metadata for the new xattr's value extent tree.
+ * The not_found path above adds credits for this tree but
+ * omits meta_add, leaving meta_ac NULL for large values.
+ */
+ if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE)
+ meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list);
+
+ /*
* This cluster will be used either for new bucket or for
* new xattr block.
* If the cluster size is the same as the bucket size, one
@@ -3470,9 +3687,10 @@ out:
}
/*
- * This function only called duing creating inode
- * for init security/acl xattrs of the new inode.
- * All transanction credits have been reserved in mknod.
+ * This helper is only for setting initial ACL or security xattrs on an inode
+ * that is still unpublished, unhashed, and unattached to a dentry.
+ * Ordinary xattr updates must use ocfs2_xattr_set().
+ * All transaction credits have been reserved in mknod or symlink callers.
*/
int ocfs2_xattr_set_handle(handle_t *handle,
struct inode *inode,
@@ -3529,8 +3747,6 @@ int ocfs2_xattr_set_handle(handle_t *handle,
xis.inode_bh = xbs.inode_bh = di_bh;
di = (struct ocfs2_dinode *)di_bh->b_data;
- down_write(&OCFS2_I(inode)->ip_xattr_sem);
-
ret = ocfs2_xattr_ibody_find(inode, name_index, name, &xis);
if (ret)
goto cleanup;
@@ -3543,7 +3759,6 @@ int ocfs2_xattr_set_handle(handle_t *handle,
ret = __ocfs2_xattr_set_handle(inode, di, &xi, &xis, &xbs, &ctxt);
cleanup:
- up_write(&OCFS2_I(inode)->ip_xattr_sem);
brelse(xbs.xattr_bh);
ocfs2_xattr_bucket_free(xbs.bucket);
@@ -6003,14 +6218,17 @@ static int ocfs2_xattr_inline_attach_refcount(struct inode *inode,
struct ocfs2_cached_dealloc_ctxt *dealloc)
{
struct ocfs2_dinode *di = (struct ocfs2_dinode *)fe_bh->b_data;
- struct ocfs2_xattr_header *header = (struct ocfs2_xattr_header *)
- (fe_bh->b_data + inode->i_sb->s_blocksize -
- le16_to_cpu(di->i_xattr_inline_size));
+ struct ocfs2_xattr_header *header;
+ int ret;
struct ocfs2_xattr_value_buf vb = {
.vb_bh = fe_bh,
.vb_access = ocfs2_journal_access_di,
};
+ ret = ocfs2_xattr_ibody_lookup_header(inode, di, &header);
+ if (ret)
+ return ret;
+
return ocfs2_xattr_attach_refcount_normal(inode, &vb, header,
ref_ci, ref_root_bh, dealloc);
}
@@ -6495,12 +6713,10 @@ static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
handle_t *handle;
struct ocfs2_super *osb = OCFS2_SB(args->old_inode->i_sb);
struct ocfs2_dinode *di = (struct ocfs2_dinode *)args->old_bh->b_data;
- int inline_size = le16_to_cpu(di->i_xattr_inline_size);
- int header_off = osb->sb->s_blocksize - inline_size;
- struct ocfs2_xattr_header *xh = (struct ocfs2_xattr_header *)
- (args->old_bh->b_data + header_off);
- struct ocfs2_xattr_header *new_xh = (struct ocfs2_xattr_header *)
- (args->new_bh->b_data + header_off);
+ int inline_size;
+ int header_off;
+ struct ocfs2_xattr_header *xh;
+ struct ocfs2_xattr_header *new_xh;
struct ocfs2_alloc_context *meta_ac = NULL;
struct ocfs2_inode_info *new_oi;
struct ocfs2_dinode *new_di;
@@ -6509,6 +6725,15 @@ static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
.vb_access = ocfs2_journal_access_di,
};
+ ret = ocfs2_xattr_ibody_lookup_header(args->old_inode, di, &xh);
+ if (ret)
+ goto out;
+
+ inline_size = le16_to_cpu(di->i_xattr_inline_size);
+ header_off = osb->sb->s_blocksize - inline_size;
+ new_xh = (struct ocfs2_xattr_header *)
+ (args->new_bh->b_data + header_off);
+
ret = ocfs2_reflink_lock_xattr_allocators(osb, xh, args->ref_root_bh,
&credits, &meta_ac);
if (ret) {
@@ -7212,10 +7437,9 @@ out_unlock:
ref_tree, 1);
brelse(ref_root_bh);
- if (ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(OCFS2_SB(old_inode->i_sb), 1);
- ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc);
- }
+ ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc);
out:
return ret;
@@ -7234,6 +7458,7 @@ int ocfs2_init_security_and_acl(struct inode *dir,
{
int ret = 0;
struct buffer_head *dir_bh = NULL;
+ struct ocfs2_acl_state acl_state = { 0 };
ret = ocfs2_init_security_get(inode, dir, qstr, NULL);
if (ret) {
@@ -7246,10 +7471,17 @@ int ocfs2_init_security_and_acl(struct inode *dir,
mlog_errno(ret);
goto leave;
}
- ret = ocfs2_init_acl(NULL, inode, dir, NULL, dir_bh, NULL, NULL);
+
+ ret = ocfs2_acl_init_prepare(inode, dir, dir_bh, &acl_state);
+ if (ret)
+ goto unlock;
+
+ ret = ocfs2_init_acl(NULL, inode, NULL, NULL, NULL, &acl_state);
if (ret)
mlog_errno(ret);
+unlock:
+ ocfs2_acl_init_release(&acl_state);
ocfs2_inode_unlock(dir, 0);
brelse(dir_bh);
leave:
diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h
index 65e9aa743919..887cc1a18b1a 100644
--- a/fs/ocfs2/xattr.h
+++ b/fs/ocfs2/xattr.h
@@ -43,6 +43,8 @@ int ocfs2_xattr_set_handle(handle_t *, struct inode *, struct buffer_head *,
struct ocfs2_alloc_context *);
int ocfs2_has_inline_xattr_value_outside(struct inode *inode,
struct ocfs2_dinode *di);
+int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno,
+ struct ocfs2_dinode *di);
int ocfs2_xattr_remove(struct inode *, struct buffer_head *);
int ocfs2_init_security_get(struct inode *, struct inode *,
const struct qstr *,
@@ -55,9 +57,12 @@ int ocfs2_init_security_set(handle_t *, struct inode *,
int ocfs2_calc_security_init(struct inode *,
struct ocfs2_security_xattr_info *,
int *, int *, struct ocfs2_alloc_context **);
-int ocfs2_calc_xattr_init(struct inode *, struct buffer_head *,
- umode_t, struct ocfs2_security_xattr_info *,
- int *, int *, int *);
+
+struct ocfs2_acl_state;
+void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode,
+ struct ocfs2_security_xattr_info *si,
+ int *want_clusters, int *xattr_credits,
+ int *want_meta, struct ocfs2_acl_state *acl_state);
/*
* xattrs can live inside an inode, as part of an external xattr block,