summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-27 13:25:11 +0100
committerMark Brown <broonie@kernel.org>2026-07-27 13:25:11 +0100
commitfed738703be77b2ff979335d0a8dab4efb59bc64 (patch)
treec107a9507433e06ffc231a847cdf3e6ce1b09fec /fs
parent1432683838d9122c62bf4a0fa139fcf26968911e (diff)
parent5bff6e212ff5715e86f51c6038ec5b1548f0d8a3 (diff)
downloadlinux-next-fed738703be77b2ff979335d0a8dab4efb59bc64.tar.gz
linux-next-fed738703be77b2ff979335d0a8dab4efb59bc64.zip
Merge branch 'mm-nonmm-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Diffstat (limited to 'fs')
-rw-r--r--fs/fat/inode.c5
-rw-r--r--fs/fat/misc.c4
-rw-r--r--fs/fat/nfs.c4
-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.c65
-rw-r--r--fs/ocfs2/dlm/dlmmaster.c6
-rw-r--r--fs/ocfs2/dlm/dlmrecovery.c9
-rw-r--r--fs/ocfs2/inode.c6
-rw-r--r--fs/ocfs2/namei.c17
-rw-r--r--fs/ocfs2/refcounttree.c47
-rw-r--r--fs/ocfs2/xattr.c238
-rw-r--r--fs/ocfs2/xattr.h2
-rw-r--r--fs/ramfs/file-nommu.c3
18 files changed, 820 insertions, 192 deletions
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 3aa52481ad5c..d1e6c9b2f9df 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -1479,8 +1479,9 @@ static int fat_read_static_bpb(struct super_block *sb,
int error = -EINVAL;
unsigned i;
- /* 16-bit DOS 1.x reliably wrote bootstrap short-jmp code */
- if (b->ignored[0] != 0xeb || b->ignored[2] != 0x90) {
+ /* 16-bit DOS 1.x reliably wrote bootstrap short-jmp or near-jmp code */
+ if ((b->ignored[0] != 0xeb || b->ignored[2] != 0x90) &&
+ (b->ignored[0] != 0xe9)) {
if (!silent)
fat_msg(sb, KERN_ERR,
"%s; no bootstrapping code", notdos1x);
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 3027ef53af21..2a0fea26a99a 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -133,7 +133,11 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
ret = fat_ent_read(inode, &fatent, last);
if (ret >= 0) {
int wait = inode_needs_sync(inode);
+ int old = ret;
+
ret = fat_ent_write(inode, &fatent, new_dclus, wait);
+ if (ret < 0)
+ fat_ent_write(inode, &fatent, old, wait);
fatent_brelse(&fatent);
}
if (ret < 0)
diff --git a/fs/fat/nfs.c b/fs/fat/nfs.c
index 509eea96a457..6e1b371711ed 100644
--- a/fs/fat/nfs.c
+++ b/fs/fat/nfs.c
@@ -250,8 +250,10 @@ struct inode *fat_rebuild_parent(struct super_block *sb, int parent_logstart)
MSDOS_I(dummy_grand_parent)->i_pos = -1;
}
- if (!fat_scan_logstart(dummy_grand_parent, clus_to_match, &sinfo))
+ if (!fat_scan_logstart(dummy_grand_parent, clus_to_match, &sinfo)) {
parent = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
+ brelse(sinfo.bh);
+ }
brelse(parent_bh);
iput(dummy_grand_parent);
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 d7fc3cccf2f4..7e09a5a4d49e 100644
--- a/fs/ocfs2/dir.c
+++ b/fs/ocfs2/dir.c
@@ -625,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:
@@ -664,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;
}
@@ -1867,6 +1904,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;
@@ -1874,9 +1912,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;
}
@@ -1970,8 +2012,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);
}
/*
@@ -2168,7 +2209,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.
*/
@@ -2181,8 +2222,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 "..".
@@ -2190,8 +2233,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/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c
index aee3b4c56dcc..612969867ff9 100644
--- a/fs/ocfs2/dlm/dlmmaster.c
+++ b/fs/ocfs2/dlm/dlmmaster.c
@@ -3099,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 9b97bf73df22..9d4a2695b959 100644
--- a/fs/ocfs2/dlm/dlmrecovery.c
+++ b/fs/ocfs2/dlm/dlmrecovery.c
@@ -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/inode.c b/fs/ocfs2/inode.c
index 662dbc845b8b..41db7dd39ed9 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -1608,6 +1608,10 @@ int ocfs2_validate_inode_block(struct super_block *sb,
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;
@@ -1948,8 +1952,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;
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 1277666c77cd..8368a7f3d4a2 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -945,7 +945,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 +1502,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 +2131,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 +2730,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 +2843,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/refcounttree.c b/fs/ocfs2/refcounttree.c
index 7323bde70caa..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;
}
@@ -3360,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;
}
@@ -3846,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
@@ -4135,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;
}
@@ -4691,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/xattr.c b/fs/ocfs2/xattr.c
index fcddd3c13acd..94ae1fb83cf1 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;
}
@@ -740,12 +764,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,41 +972,206 @@ static int ocfs2_xattr_list_entries(struct inode *inode,
return result;
}
-static int ocfs2_xattr_ibody_lookup_header(struct inode *inode,
- struct ocfs2_dinode *di,
- struct ocfs2_xattr_header **header)
+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 > inode->i_sb->s_blocksize ||
+ if (inline_size > 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);
+ ocfs2_error(sb,
+ "Invalid inode %llu: xattr inline size %u\n",
+ (unsigned long long)blkno, inline_size);
return -EFSCORRUPTED;
}
- *header = (struct ocfs2_xattr_header *)
- ((void *)di + inode->i_sb->s_blocksize - inline_size);
+ xh = (struct ocfs2_xattr_header *)
+ ((void *)di + sb->s_blocksize - inline_size);
- xattr_count = le16_to_cpu((*header)->xh_count);
+ 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(inode->i_sb,
+ ocfs2_error(sb,
"xattr entry count %u exceeds maximum %zu in inode %llu\n",
xattr_count, max_entries,
- (unsigned long long)OCFS2_I(inode)->ip_blkno);
+ (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)
{
@@ -3255,6 +3442,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
@@ -7235,10 +7430,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;
diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h
index 65e9aa743919..6b7589941315 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 *,
diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c
index 2f79bcb89d2e..fb471bf88ab7 100644
--- a/fs/ramfs/file-nommu.c
+++ b/fs/ramfs/file-nommu.c
@@ -69,6 +69,9 @@ int ramfs_nommu_expand_for_mapping(struct inode *inode, size_t newsize)
gfp_t gfp = mapping_gfp_mask(inode->i_mapping);
/* make various checks */
+ if (!newsize)
+ return 0;
+
order = get_order(newsize);
if (unlikely(order > MAX_PAGE_ORDER))
return -EFBIG;