summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-30 11:00:25 +0000
committerMark Brown <broonie@kernel.org>2026-10-30 11:00:25 +0000
commitf7e88613fdcbef305e9f46ccd22c362d733bb177 (patch)
tree47cac24f4f577c1ddac0713a7117b40242e9ec5f
parentedb6c17a94809770a3632e97b295511f4112bf14 (diff)
parent07b36d7b86ec3cd8d4a399d71db049803ab3a7f1 (diff)
downloadlinux-next-f7e88613fdcbef305e9f46ccd22c362d733bb177.tar.gz
linux-next-f7e88613fdcbef305e9f46ccd22c362d733bb177.zip
Merge branch 'mm-nonmm-hotfixes-unstable' of https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
-rw-r--r--.mailmap4
-rw-r--r--MAINTAINERS14
-rw-r--r--fs/fat/dir.c10
-rw-r--r--fs/fat/fat.h1
-rw-r--r--fs/fat/file.c70
-rw-r--r--fs/fat/inode.c2
-rw-r--r--fs/fat/namei_msdos.c9
-rw-r--r--fs/fat/namei_vfat.c9
-rw-r--r--fs/ocfs2/cluster/heartbeat.c40
-rw-r--r--fs/ocfs2/cluster/tcp.c5
-rw-r--r--fs/ocfs2/dlm/dlmmaster.c70
-rw-r--r--fs/ocfs2/dlmglue.c18
-rw-r--r--fs/ocfs2/journal.c15
-rw-r--r--fs/ocfs2/journal.h1
-rw-r--r--fs/ocfs2/move_extents.c70
-rw-r--r--fs/ocfs2/super.c3
-rw-r--r--fs/ocfs2/xattr.c146
-rw-r--r--kernel/panic.c2
18 files changed, 376 insertions, 113 deletions
diff --git a/.mailmap b/.mailmap
index 0d05be949eff..5ab2cf568d3d 100644
--- a/.mailmap
+++ b/.mailmap
@@ -233,6 +233,10 @@ Chuck Lever <cel@kernel.org> <chuck.lever@oracle.com>
Chuck Lever <cel@kernel.org> <cel@netapp.com>
Chuck Lever <cel@kernel.org> <cel@citi.umich.edu>
Claudiu Beznea <claudiu.beznea@tuxon.dev> <claudiu.beznea@microchip.com>
+Clément Léger <clement.leger@linux.dev> <cleger@kalray.eu>
+Clément Léger <clement.leger@linux.dev> <clement.leger@bootlin.com>
+Clément Léger <clement.leger@linux.dev> <cleger@rivosinc.com>
+Clément Léger <clement.leger@linux.dev> <cleger@meta.com>
Coiby Xu <coiby.xu@gmail.com> <coxu@redhat.com>
Colin Ian King <colin.i.king@gmail.com> <colin.king@canonical.com>
Corey Minyard <minyard@acm.org>
diff --git a/MAINTAINERS b/MAINTAINERS
index cfb27a142c59..8f4faed17f83 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14042,6 +14042,16 @@ S: Maintained
F: io_uring/bpf-ops.*
F: io_uring/loop.*
+IPC
+M: Andrew Morton <akpm@linux-foundation.org>
+R: Manfred Spraul <manfred@colorfullife.com>
+R: Davidlohr Bueso <dave@stgolabs.net>
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: ipc/
+F: tools/testing/selftests/ipc/
+F: tools/testing/selftests/mqueue/
+
IPMI SUBSYSTEM
M: Corey Minyard <corey@minyard.net>
L: openipmi-developer@lists.sourceforge.net (moderated for non-subscribers)
@@ -20885,7 +20895,7 @@ S: Maintained
F: drivers/char/hw_random/optee-rng.c
OP-TEE RTC DRIVER
-M: Clément Léger <clement.leger@bootlin.com>
+M: Clément Léger <clement.leger@linux.dev>
L: linux-rtc@vger.kernel.org
S: Maintained
F: drivers/rtc/rtc-optee.c
@@ -23783,7 +23793,7 @@ F: Documentation/devicetree/bindings/rtc/renesas,rz-rtca3.yaml
F: drivers/rtc/rtc-renesas-rtca3.c
RENESAS RZ/N1 A5PSW SWITCH DRIVER
-M: Clément Léger <clement.leger@bootlin.com>
+M: Clément Léger <clement.leger@linux.dev>
L: linux-renesas-soc@vger.kernel.org
L: netdev@vger.kernel.org
S: Maintained
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index 35bdb62944a2..0cebcaf82426 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -963,6 +963,16 @@ int fat_dir_empty(struct inode *dir)
}
EXPORT_SYMBOL_GPL(fat_dir_empty);
+void fat_dir_drop_nlink(struct inode *dir)
+{
+ if (dir->i_nlink >= 3)
+ drop_nlink(dir);
+ else
+ fat_fs_error(dir->i_sb, "parent dir link count too low (%u)",
+ dir->i_nlink);
+}
+EXPORT_SYMBOL_GPL(fat_dir_drop_nlink);
+
/*
* fat_subdirs counts the number of sub-directories of dir. It can be run
* on directories being created.
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 76978a268410..a8a3ca4f465b 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -332,6 +332,7 @@ extern const struct file_operations fat_dir_operations;
extern int fat_search_long(struct inode *inode, const unsigned char *name,
int name_len, struct fat_slot_info *sinfo);
extern int fat_dir_empty(struct inode *dir);
+void fat_dir_drop_nlink(struct inode *dir);
extern int fat_subdirs(struct inode *dir);
extern int fat_scan(struct inode *dir, const unsigned char *name,
struct fat_slot_info *sinfo);
diff --git a/fs/fat/file.c b/fs/fat/file.c
index d4f1b360b974..70583e791bdc 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -217,13 +217,62 @@ const struct file_operations fat_file_operations = {
.setlease = generic_setlease,
};
+static int fat_preallocate(struct inode *inode, loff_t new_size)
+{
+ int nr_cluster; /* Number of clusters to be allocated */
+ loff_t mm_bytes; /* Number of bytes to be allocated for file */
+ loff_t ondisksize; /* block aligned on-disk size in bytes*/
+ struct super_block *sb = inode->i_sb;
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+
+ ondisksize = inode->i_blocks << 9;
+ if (new_size <= ondisksize)
+ return 0;
+
+ /* First compute the number of clusters to be allocated */
+ mm_bytes = new_size - ondisksize;
+ nr_cluster = (mm_bytes + (sbi->cluster_size - 1)) >>
+ sbi->cluster_bits;
+
+ /* Allocate the clusters. We are not zeroing them out */
+ return fat_add_clusters(inode, nr_cluster);
+}
+
+static int fat_cont_expand_simple(struct inode *inode, loff_t size)
+{
+ struct address_space *mapping = inode->i_mapping;
+ const struct address_space_operations *aops = mapping->a_ops;
+ struct folio *folio;
+ void *fsdata = NULL;
+ int err;
+
+ err = aops->write_begin(NULL, mapping, size, 0, &folio, &fsdata);
+ if (err)
+ goto out;
+
+ err = aops->write_end(NULL, mapping, size, 0, 0, folio, fsdata);
+
+out:
+ return err;
+}
+
static int fat_cont_expand(struct inode *inode, loff_t size)
{
struct address_space *mapping = inode->i_mapping;
loff_t start = inode->i_size, count = size - inode->i_size;
int err;
- err = generic_cont_expand_simple(inode, size);
+ /*
+ * Preallocating space here is more efficient than during
+ * fat_cont_expand_simple() because of batching.
+ */
+ err = inode_newsize_ok(inode, size);
+ if (err)
+ goto out;
+ err = fat_preallocate(inode, size);
+ if (err)
+ goto out;
+ err = fat_cont_expand_simple(inode, size);
if (err)
goto out;
@@ -263,12 +312,7 @@ out:
static long fat_fallocate(struct file *file, int mode,
loff_t offset, loff_t len)
{
- int nr_cluster; /* Number of clusters to be allocated */
- loff_t mm_bytes; /* Number of bytes to be allocated for file */
- loff_t ondisksize; /* block aligned on-disk size in bytes*/
struct inode *inode = file->f_mapping->host;
- struct super_block *sb = inode->i_sb;
- struct msdos_sb_info *sbi = MSDOS_SB(sb);
int err = 0;
/* No support for hole punch or other fallocate flags. */
@@ -281,19 +325,7 @@ static long fat_fallocate(struct file *file, int mode,
inode_lock(inode);
if (mode & FALLOC_FL_KEEP_SIZE) {
- ondisksize = inode->i_blocks << 9;
- if ((offset + len) <= ondisksize)
- goto error;
-
- /* First compute the number of clusters to be allocated */
- mm_bytes = offset + len - ondisksize;
- nr_cluster = (mm_bytes + (sbi->cluster_size - 1)) >>
- sbi->cluster_bits;
-
- /* Allocate the clusters. We are not zeroing them out */
- err = fat_add_clusters(inode, nr_cluster);
- if (err)
- goto error;
+ err = fat_preallocate(inode, offset + len);
} else {
if ((offset + len) <= i_size_read(inode))
goto error;
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index d85c8fbdcca4..353c91f0a843 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -217,7 +217,9 @@ static void fat_write_failed(struct address_space *mapping, loff_t to)
if (to > inode->i_size) {
truncate_pagecache(inode, inode->i_size);
+ down_write(&MSDOS_I(inode)->truncate_lock);
fat_truncate_blocks(inode, inode->i_size);
+ up_write(&MSDOS_I(inode)->truncate_lock);
}
}
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index c7500c2859e0..5a43d042eb01 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -333,12 +333,7 @@ static int msdos_rmdir(struct inode *dir, struct dentry *dentry)
err = fat_remove_entries(dir, &sinfo); /* and releases bh */
if (err)
goto out;
- if (dir->i_nlink >= 3)
- drop_nlink(dir);
- else {
- fat_fs_error(sb, "parent dir link count too low (%u)",
- dir->i_nlink);
- }
+ fat_dir_drop_nlink(dir);
clear_nlink(inode);
fat_detach(inode);
@@ -565,6 +560,8 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
(void)sync_inode_metadata(old_dir, 1);
if (new_inode) {
+ if (is_dir && old_dir == new_dir)
+ fat_dir_drop_nlink(old_dir);
drop_nlink(new_inode);
if (is_dir)
drop_nlink(new_inode);
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index ff7652bf0410..791f2d7b8509 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -809,12 +809,7 @@ static int vfat_rmdir(struct inode *dir, struct dentry *dentry)
err = fat_remove_entries(dir, &sinfo); /* and releases bh */
if (err)
goto out;
- if (dir->i_nlink >= 3)
- drop_nlink(dir);
- else {
- fat_fs_error(sb, "parent dir link count too low (%u)",
- dir->i_nlink);
- }
+ fat_dir_drop_nlink(dir);
clear_nlink(inode);
fat_truncate_time(inode, NULL, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
@@ -1011,6 +1006,8 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
vfat_update_dir_metadata(old_dir, &ts);
if (new_inode) {
+ if (is_dir && old_dir == new_dir)
+ fat_dir_drop_nlink(old_dir);
drop_nlink(new_inode);
if (is_dir)
drop_nlink(new_inode);
diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c
index b45adc39aacc..7a18e71b507b 100644
--- a/fs/ocfs2/cluster/heartbeat.c
+++ b/fs/ocfs2/cluster/heartbeat.c
@@ -269,6 +269,8 @@ struct o2hb_region {
struct list_head hr_handler_list;
/* Serializes timeout arming against failed-start and teardown. */
struct mutex hr_arming_mutex;
+ /* Serializes the dev, block_bytes, start_block and blocks attributes. */
+ struct mutex hr_dev_mutex;
bool hr_stopping;
/* last hb status, 0 for success, other value for error. */
@@ -1681,6 +1683,7 @@ static ssize_t o2hb_region_block_bytes_store(struct config_item *item,
unsigned long block_bytes;
unsigned int block_bits;
+ guard(mutex)(&reg->hr_dev_mutex);
if (reg->hr_bdev_file)
return -EINVAL;
@@ -1710,6 +1713,7 @@ static ssize_t o2hb_region_start_block_store(struct config_item *item,
char *p = (char *)page;
ssize_t ret;
+ guard(mutex)(&reg->hr_dev_mutex);
if (reg->hr_bdev_file)
return -EINVAL;
@@ -1736,6 +1740,7 @@ static ssize_t o2hb_region_blocks_store(struct config_item *item,
char *p = (char *)page;
int ret;
+ guard(mutex)(&reg->hr_dev_mutex);
if (reg->hr_bdev_file)
return -EINVAL;
@@ -1753,10 +1758,12 @@ static ssize_t o2hb_region_blocks_store(struct config_item *item,
static ssize_t o2hb_region_dev_show(struct config_item *item, char *page)
{
+ struct o2hb_region *reg = to_o2hb_region(item);
unsigned int ret = 0;
- if (to_o2hb_region(item)->hr_bdev_file)
- ret = sprintf(page, "%pg\n", reg_bdev(to_o2hb_region(item)));
+ guard(mutex)(&reg->hr_dev_mutex);
+ if (reg->hr_bdev_file)
+ ret = sprintf(page, "%pg\n", reg_bdev(reg));
return ret;
}
@@ -1872,11 +1879,9 @@ out:
* this is acting as commit; we set up all of hr_bdev_file and hr_task or
* nothing
*/
-static ssize_t o2hb_region_dev_store(struct config_item *item,
- const char *page,
- size_t count)
+static ssize_t __o2hb_region_dev_store(struct o2hb_region *reg,
+ const char *page, size_t count)
{
- struct o2hb_region *reg = to_o2hb_region(item);
struct task_struct *hb_task;
long fd;
int sectsize;
@@ -1884,6 +1889,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
ssize_t ret = -EINVAL;
int live_threshold;
+ lockdep_assert_held(&reg->hr_dev_mutex);
+
if (reg->hr_bdev_file)
return -EINVAL;
@@ -1987,9 +1994,15 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
wake_up_process(hb_task);
+ /*
+ * Don't hold hr_dev_mutex across the wait, which can take seconds.
+ * hr_bdev_file is already set, so other writers still get -EINVAL.
+ */
+ mutex_unlock(&reg->hr_dev_mutex);
ret = wait_event_interruptible(o2hb_steady_queue,
atomic_read(&reg->hr_steady_iterations) == 0 ||
reg->hr_node_deleted);
+ mutex_lock(&reg->hr_dev_mutex);
if (ret) {
atomic_set(&reg->hr_steady_iterations, 0);
reg->hr_aborted_start = 1;
@@ -2041,6 +2054,20 @@ out:
return ret;
}
+static ssize_t o2hb_region_dev_store(struct config_item *item,
+ const char *page,
+ size_t count)
+{
+ struct o2hb_region *reg = to_o2hb_region(item);
+ ssize_t ret;
+
+ mutex_lock(&reg->hr_dev_mutex);
+ ret = __o2hb_region_dev_store(reg, page, count);
+ mutex_unlock(&reg->hr_dev_mutex);
+
+ return ret;
+}
+
static ssize_t o2hb_region_pid_show(struct config_item *item, char *page)
{
struct o2hb_region *reg = to_o2hb_region(item);
@@ -2143,6 +2170,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
INIT_LIST_HEAD(&reg->hr_all_item);
mutex_init(&reg->hr_arming_mutex);
+ mutex_init(&reg->hr_dev_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);
diff --git a/fs/ocfs2/cluster/tcp.c b/fs/ocfs2/cluster/tcp.c
index 8b3830b601ba..0eaf2c4a6989 100644
--- a/fs/ocfs2/cluster/tcp.c
+++ b/fs/ocfs2/cluster/tcp.c
@@ -393,6 +393,9 @@ static void sc_kref_release(struct kref *kref)
sclog(sc, "releasing\n");
+ /* Unlink before freeing anything the debugfs readers dereference. */
+ o2net_debug_del_sc(sc);
+
if (sc->sc_sock) {
sock_release(sc->sc_sock);
sc->sc_sock = NULL;
@@ -402,8 +405,6 @@ static void sc_kref_release(struct kref *kref)
o2nm_node_put(sc->sc_node);
sc->sc_node = NULL;
- o2net_debug_del_sc(sc);
-
if (sc->sc_page)
__free_page(sc->sc_page);
kfree(sc);
diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c
index 612969867ff9..ea48c013f7fb 100644
--- a/fs/ocfs2/dlm/dlmmaster.c
+++ b/fs/ocfs2/dlm/dlmmaster.c
@@ -2525,6 +2525,52 @@ static int dlm_is_lockres_migratable(struct dlm_ctxt *dlm,
*/
+/*
+ * Drop the sender's references on a migration MLE after the attempt has
+ * failed. The finishing assert from the target may already have unlinked
+ * the MLE and consumed its base reference; decide under the locks the
+ * assert handler holds, and only drop the base reference while the MLE is
+ * still hashed. Returns true when the assert got there first: the target
+ * has become the master, so the caller must complete the migration.
+ */
+static bool dlm_put_migration_mle(struct dlm_ctxt *dlm,
+ struct dlm_master_list_entry *mle)
+{
+ bool asserted;
+
+ spin_lock(&dlm->spinlock);
+ spin_lock(&dlm->master_lock);
+ __dlm_mle_detach_hb_events(dlm, mle);
+ asserted = hlist_unhashed(&mle->master_hash_node);
+ if (!asserted) {
+ __dlm_unlink_mle(dlm, mle);
+ __dlm_put_mle(mle);
+ }
+ spin_unlock(&dlm->master_lock);
+ spin_unlock(&dlm->spinlock);
+
+ dlm_put_mle_inuse(mle);
+ return asserted;
+}
+
+/*
+ * The finishing assert got there first: it switched the owner to the
+ * target and cleared DLM_LOCK_RES_MIGRATING. Drop the other nodes' locks
+ * as a completed migration does, unless recovery has taken the lockres
+ * over since, and let the usage be recalculated.
+ */
+static void dlm_migrate_lockres_asserted(struct dlm_ctxt *dlm,
+ struct dlm_lock_resource *res,
+ u8 target)
+{
+ spin_lock(&res->spinlock);
+ if (res->owner == target)
+ dlm_remove_nonlocal_locks(dlm, res);
+ spin_unlock(&res->spinlock);
+
+ dlm_lockres_calc_usage(dlm, res);
+}
+
static int dlm_migrate_lockres(struct dlm_ctxt *dlm,
struct dlm_lock_resource *res, u8 target)
{
@@ -2609,9 +2655,10 @@ fail:
if (ret < 0) {
if (mle_added) {
- dlm_mle_detach_hb_events(dlm, mle);
- dlm_put_mle(mle);
- dlm_put_mle_inuse(mle);
+ if (dlm_put_migration_mle(dlm, mle)) {
+ dlm_migrate_lockres_asserted(dlm, res, target);
+ ret = 0;
+ }
} else if (mle) {
kmem_cache_free(dlm_mle_cache, mle);
mle = NULL;
@@ -2640,9 +2687,11 @@ fail:
mlog(0, "migration to node %u failed with %d\n",
target, ret);
/* migration failed, detach and clean up mle */
- dlm_mle_detach_hb_events(dlm, mle);
- dlm_put_mle(mle);
- dlm_put_mle_inuse(mle);
+ if (dlm_put_migration_mle(dlm, mle)) {
+ dlm_migrate_lockres_asserted(dlm, res, target);
+ ret = 0;
+ goto leave;
+ }
spin_lock(&res->spinlock);
res->state &= ~DLM_LOCK_RES_MIGRATING;
wake = 1;
@@ -2686,9 +2735,12 @@ fail:
res->lockname.name, target);
ret = -EINVAL;
/* migration failed, detach and clean up mle */
- dlm_mle_detach_hb_events(dlm, mle);
- dlm_put_mle(mle);
- dlm_put_mle_inuse(mle);
+ if (dlm_put_migration_mle(dlm, mle)) {
+ dlm_migrate_lockres_asserted(dlm, res,
+ target);
+ ret = 0;
+ goto leave;
+ }
spin_lock(&res->spinlock);
res->state &= ~DLM_LOCK_RES_MIGRATING;
wake = 1;
diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c
index 88d946466083..412ab0808c59 100644
--- a/fs/ocfs2/dlmglue.c
+++ b/fs/ocfs2/dlmglue.c
@@ -3356,6 +3356,14 @@ static void ocfs2_dlm_shutdown_debug(struct ocfs2_super *osb)
{
struct ocfs2_dlm_debug *dlm_debug = osb->osb_dlm_debug;
+ /*
+ * Drain the readers before the cluster stack goes away: the
+ * locking_state reader resolves ocfs2_dlm_lvb() through the
+ * stack. Every caller removes the parent directory later.
+ */
+ debugfs_lookup_and_remove("locking_state", osb->osb_debug_root);
+ debugfs_lookup_and_remove("locking_filter", osb->osb_debug_root);
+
if (dlm_debug)
ocfs2_put_dlm_debug(dlm_debug);
}
@@ -3370,8 +3378,6 @@ int ocfs2_dlm_init(struct ocfs2_super *osb)
goto local;
}
- ocfs2_dlm_init_debug(osb);
-
/* launch downconvert thread */
osb->dc_task = kthread_run(ocfs2_downconvert_thread, osb, "ocfs2dc-%s",
osb->uuid_str);
@@ -3411,9 +3417,10 @@ local:
ocfs2_orphan_scan_lock_res_init(&osb->osb_orphan_scan.os_lockres, osb);
osb->cconn = conn;
+ if (conn)
+ ocfs2_dlm_init_debug(osb);
bail:
if (status < 0) {
- ocfs2_dlm_shutdown_debug(osb);
if (osb->dc_task)
kthread_stop(osb->dc_task);
}
@@ -3443,10 +3450,11 @@ void ocfs2_dlm_shutdown(struct ocfs2_super *osb,
ocfs2_lock_res_free(&osb->osb_orphan_scan.os_lockres);
if (osb->cconn) {
+ /* The disconnect below may release the selected stack. */
+ ocfs2_dlm_shutdown_debug(osb);
+
ocfs2_cluster_disconnect(osb->cconn, hangup_pending);
osb->cconn = NULL;
-
- ocfs2_dlm_shutdown_debug(osb);
}
}
diff --git a/fs/ocfs2/journal.c b/fs/ocfs2/journal.c
index cc60db6b9706..cbb44e280d4b 100644
--- a/fs/ocfs2/journal.c
+++ b/fs/ocfs2/journal.c
@@ -227,13 +227,22 @@ void ocfs2_recovery_disable_quota(struct ocfs2_super *osb)
ocfs2_recovery_disable(osb, OCFS2_REC_QUOTA_WANT_DISABLE);
}
+/* Stop producers and drain completion work without freeing recovery state. */
+void ocfs2_recovery_stop(struct ocfs2_super *osb)
+{
+ ocfs2_recovery_disable(osb, OCFS2_REC_WANT_DISABLE);
+}
+
void ocfs2_recovery_exit(struct ocfs2_super *osb)
{
struct ocfs2_recovery_map *rm;
- /* disable any new recovery threads and wait for any currently
- * running ones to exit. Do this before setting the vol_state. */
- ocfs2_recovery_disable(osb, OCFS2_REC_WANT_DISABLE);
+ /*
+ * Disable producers and drain work before freeing recovery state.
+ * Do this before setting vol_state to VOLUME_DISMOUNTED: completion
+ * work may still be waiting in __ocfs2_wait_on_mount().
+ */
+ ocfs2_recovery_stop(osb);
/*
* Now that recovery is shut down, and the osb is about to be
diff --git a/fs/ocfs2/journal.h b/fs/ocfs2/journal.h
index 19fc920d26b1..4b52bf7b0938 100644
--- a/fs/ocfs2/journal.h
+++ b/fs/ocfs2/journal.h
@@ -147,6 +147,7 @@ void ocfs2_complete_recovery(struct work_struct *work);
void ocfs2_wait_for_recovery(struct ocfs2_super *osb);
int ocfs2_recovery_init(struct ocfs2_super *osb);
+void ocfs2_recovery_stop(struct ocfs2_super *osb);
void ocfs2_recovery_exit(struct ocfs2_super *osb);
void ocfs2_recovery_disable_quota(struct ocfs2_super *osb);
diff --git a/fs/ocfs2/move_extents.c b/fs/ocfs2/move_extents.c
index ad1678ee7cc4..93b45531bf7d 100644
--- a/fs/ocfs2/move_extents.c
+++ b/fs/ocfs2/move_extents.c
@@ -907,6 +907,30 @@ out:
return ret;
}
+static int ocfs2_validate_and_adjust_move_range(struct inode *inode,
+ struct ocfs2_move_extents *range)
+{
+ u64 i_size = i_size_read(inode);
+
+ if (range->me_start > i_size)
+ return -EINVAL;
+
+ if (range->me_len > i_size - range->me_start)
+ range->me_len = i_size - range->me_start;
+
+ /*
+ * The default threshold for defragmentation is 1 MiB, which
+ * matches the maximum OCFS2 cluster size.
+ */
+ if (!range->me_threshold)
+ range->me_threshold = 1024 * 1024;
+
+ if (range->me_threshold > i_size)
+ range->me_threshold = i_size;
+
+ return 0;
+}
+
static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
{
int status;
@@ -936,6 +960,21 @@ static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
goto out_rw_unlock;
}
+ status = ocfs2_validate_and_adjust_move_range(inode, context->range);
+ if (status) {
+ mlog_errno(status);
+ goto out_inode_unlock;
+ }
+
+ /* The goal check depends on the adjusted range length. */
+ if (!context->auto_defrag) {
+ status = ocfs2_validate_and_adjust_move_goal(inode, context->range);
+ if (status) {
+ mlog_errno(status);
+ goto out_inode_unlock;
+ }
+ }
+
/*
* remember ip_xattr_sem also needs to be held if necessary
*/
@@ -1028,27 +1067,8 @@ int ocfs2_ioctl_move_extents(struct file *filp, void __user *argp)
goto out_free;
}
- if (range.me_start > i_size_read(inode)) {
- status = -EINVAL;
- goto out_free;
- }
-
- if (range.me_start + range.me_len > i_size_read(inode))
- range.me_len = i_size_read(inode) - range.me_start;
-
context->range = &range;
- /*
- * ok, the default threshold for the defragmentation
- * is 1M, since our maximum clustersize was 1M also.
- * any thought?
- */
- if (!range.me_threshold)
- range.me_threshold = 1024 * 1024;
-
- if (range.me_threshold > i_size_read(inode))
- range.me_threshold = i_size_read(inode);
-
if (range.me_flags & ~(OCFS2_MOVE_EXT_FL_AUTO_DEFRAG |
OCFS2_MOVE_EXT_FL_PART_DEFRAG)) {
status = -EINVAL;
@@ -1060,23 +1080,11 @@ int ocfs2_ioctl_move_extents(struct file *filp, void __user *argp)
if (range.me_flags & OCFS2_MOVE_EXT_FL_PART_DEFRAG)
context->partial = 1;
- } else {
- /*
- * first best-effort attempt to validate and adjust the goal
- * (physical address in block), while it can't guarantee later
- * operation can succeed all the time since global_bitmap may
- * change a bit over time.
- */
-
- status = ocfs2_validate_and_adjust_move_goal(inode, &range);
- if (status)
- goto out_copy;
}
status = ocfs2_move_extents(context);
if (status)
mlog_errno(status);
-out_copy:
/*
* movement/defragmentation may end up being partially completed,
* that's the reason why we need to return userspace the finished
diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c
index 0af2245ae303..3072b0386db8 100644
--- a/fs/ocfs2/super.c
+++ b/fs/ocfs2/super.c
@@ -1831,6 +1831,9 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err)
/* Wait for worker to be done with the work structure in osb */
cancel_work_sync(&osb->dquot_drop_work);
+ /* Stop recovery producers before releasing their local allocators. */
+ ocfs2_recovery_stop(osb);
+
ocfs2_shutdown_local_alloc(osb);
ocfs2_truncate_log_shutdown(osb);
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index b2bd06083a14..ac41653e59f5 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -275,6 +275,7 @@ static int ocfs2_xattr_tree_list_index_block(struct inode *inode,
size_t buffer_size);
static int ocfs2_xattr_create_index_block(struct inode *inode,
+ struct ocfs2_xattr_info *xi,
struct ocfs2_xattr_search *xs,
struct ocfs2_xattr_set_ctxt *ctxt);
@@ -3375,7 +3376,7 @@ static int ocfs2_xattr_block_set(struct inode *inode,
else if ((ret != -ENOSPC) || ctxt->set_abort)
goto end;
else {
- ret = ocfs2_xattr_create_index_block(inode, xs, ctxt);
+ ret = ocfs2_xattr_create_index_block(inode, xi, xs, ctxt);
if (ret)
goto end;
}
@@ -4704,6 +4705,21 @@ static void ocfs2_cp_xattr_block_to_bucket(struct inode *inode,
memset(bucket_block(bucket, i), 0, blocksize);
/*
+ * An empty block has no names or values to carry over, and the
+ * xh_entries[count - 1] below would be xh_entries[-1]. Leave the
+ * bucket empty, but carry over the hash ocfs2_xa_remove_entry() left
+ * in the block's xh_entries[0] so that the bucket still holds the key
+ * it is looked up by.
+ */
+ if (!count) {
+ xh->xh_num_buckets = cpu_to_le16(1);
+ xh->xh_free_start = cpu_to_le16(OCFS2_XATTR_BUCKET_SIZE);
+ xh->xh_entries[0].xe_name_hash =
+ xb_xh->xh_entries[0].xe_name_hash;
+ return;
+ }
+
+ /*
* Since the xe_name_offset is based on ocfs2_xattr_header,
* there is a offset change corresponding to the change of
* ocfs2_xattr_header's position.
@@ -4749,13 +4765,10 @@ static void ocfs2_cp_xattr_block_to_bucket(struct inode *inode,
* real place of the xattr.
*/
static void ocfs2_xattr_update_xattr_search(struct inode *inode,
- struct ocfs2_xattr_search *xs,
- struct buffer_head *old_bh)
+ struct ocfs2_xattr_info *xi,
+ struct ocfs2_xattr_search *xs)
{
- char *buf = old_bh->b_data;
- struct ocfs2_xattr_block *old_xb = (struct ocfs2_xattr_block *)buf;
- struct ocfs2_xattr_header *old_xh = &old_xb->xb_attrs.xb_header;
- int i;
+ int i, count;
xs->header = bucket_xh(xs->bucket);
xs->base = bucket_block(xs->bucket, 0);
@@ -4764,8 +4777,43 @@ static void ocfs2_xattr_update_xattr_search(struct inode *inode,
if (xs->not_found)
return;
- i = xs->here - old_xh->xh_entries;
- xs->here = &xs->header->xh_entries[i];
+ /*
+ * A block holds its entries in insertion order while a bucket holds
+ * them by name hash, so ocfs2_cp_xattr_block_to_bucket() sorted the
+ * copies and the index this entry had in the block no longer says
+ * where it is. It copied every entry and every name unchanged, and
+ * (type, name) pairs are unique within a block, so find it again by
+ * name.
+ *
+ * Search for the name being set, not for the name xs->here carries.
+ * A failed set may have removed that entry, and ocfs2_xa_remove_entry()
+ * then memmoves its successor into the slot. Comparing every entry
+ * rather than narrowing on the hash also finds an entry stored under
+ * the legacy signed hash, which ocfs2_find_xe_in_bucket() would skip.
+ */
+ count = le16_to_cpu(xs->header->xh_count);
+ for (i = 0; i < count; i++) {
+ struct ocfs2_xattr_entry *xe = &xs->header->xh_entries[i];
+
+ if (ocfs2_xattr_get_type(xe) != xi->xi_name_index)
+ continue;
+ if (xe->xe_name_len != xi->xi_name_len)
+ continue;
+ if (!memcmp(xs->base + le16_to_cpu(xe->xe_name_offset),
+ xi->xi_name, xi->xi_name_len)) {
+ xs->here = xe;
+ return;
+ }
+ }
+
+ /*
+ * The entry did not survive the failed set, so this has become an
+ * insert. Point xs->here back inside the bucket: it still refers to
+ * the block being converted, which is about to be rewritten as a tree
+ * root.
+ */
+ xs->not_found = -ENODATA;
+ xs->here = xs->header->xh_entries;
}
/*
@@ -4773,6 +4821,7 @@ static void ocfs2_xattr_update_xattr_search(struct inode *inode,
* is allocated and the xattr block header is rewritten.
*/
static int ocfs2_xattr_create_index_block(struct inode *inode,
+ struct ocfs2_xattr_info *xi,
struct ocfs2_xattr_search *xs,
struct ocfs2_xattr_set_ctxt *ctxt)
{
@@ -4834,7 +4883,7 @@ static int ocfs2_xattr_create_index_block(struct inode *inode,
ocfs2_cp_xattr_block_to_bucket(inode, xb_bh, xs->bucket);
ocfs2_xattr_bucket_journal_dirty(handle, xs->bucket);
- ocfs2_xattr_update_xattr_search(inode, xs, xb_bh);
+ ocfs2_xattr_update_xattr_search(inode, xi, xs);
/* Change from ocfs2_xattr_header to ocfs2_xattr_tree_root */
memset(&xb->xb_attrs, 0, inode->i_sb->s_blocksize -
@@ -5194,7 +5243,15 @@ static int ocfs2_divide_xattr_bucket(struct inode *inode,
start = ocfs2_xattr_find_divide_pos(xh);
if (start == count) {
- xe = &xh->xh_entries[start-1];
+ /*
+ * start == count means every entry shares one hash, so the
+ * first of them carries it too. An empty bucket has no
+ * entries[start - 1], but its entries[0] still carries the
+ * bucket key: either the hash ocfs2_xa_remove_entry()
+ * deliberately leaves behind, or the sentinel a previous
+ * divide wrote.
+ */
+ xe = &xh->xh_entries[0];
/*
* initialized a new empty bucket here.
@@ -6027,11 +6084,20 @@ static int ocfs2_check_xattr_bucket_collision(struct ocfs2_xattr_bucket *bucket,
u32 name_hash)
{
struct ocfs2_xattr_header *xh = bucket_xh(bucket);
+ u16 count = le16_to_cpu(xh->xh_count);
+
+ /*
+ * An empty bucket keeps the hash of the entry that was removed in
+ * xh_entries[0] so that it can still be found, but it holds no
+ * entries to collide with.
+ */
+ if (!count)
+ return 0;
if (name_hash != le32_to_cpu(xh->xh_entries[0].xe_name_hash))
return 0;
- if (xh->xh_entries[le16_to_cpu(xh->xh_count) - 1].xe_name_hash ==
+ if (xh->xh_entries[count - 1].xe_name_hash ==
xh->xh_entries[0].xe_name_hash) {
mlog(ML_ERROR, "Too much hash collision in xattr bucket %llu, "
"hash = %u\n",
@@ -6052,13 +6118,18 @@ static int ocfs2_xattr_set_entry_bucket(struct inode *inode,
struct ocfs2_xattr_search *xs,
struct ocfs2_xattr_set_ctxt *ctxt)
{
- int ret;
+ int ret, found = 0;
+ u16 xe_index;
+ u32 name_hash;
struct ocfs2_xa_loc loc;
trace_ocfs2_xattr_set_entry_bucket(xi->xi_name);
ocfs2_init_xattr_bucket_xa_loc(&loc, xs->bucket,
xs->not_found ? NULL : xs->here);
+ /* Defragmenting below may move this entry, so remember its hash. */
+ name_hash = loc.xl_entry ? le32_to_cpu(loc.xl_entry->xe_name_hash) : 0;
+
ret = ocfs2_xa_set(&loc, xi, ctxt);
if (!ret) {
xs->here = loc.xl_entry;
@@ -6077,6 +6148,29 @@ static int ocfs2_xattr_set_entry_bucket(struct inode *inode,
goto out;
}
+ /*
+ * ocfs2_defrag_xattr_bucket() sorts the entries by name hash and
+ * lib/sort.c is not stable, so entries sharing a hash may have traded
+ * slots. Look the entry up again before writing through it.
+ */
+ if (loc.xl_entry) {
+ ret = ocfs2_find_xe_in_bucket(inode, xs->bucket,
+ xi->xi_name_index, xi->xi_name,
+ name_hash, &xe_index, &found);
+ if (ret) {
+ mlog_errno(ret);
+ goto out;
+ }
+ if (!found) {
+ mlog(ML_ERROR, "xattr entry not found in bucket %llu after defrag\n",
+ (unsigned long long)bucket_blkno(xs->bucket));
+ ret = -EFSCORRUPTED;
+ goto out;
+ }
+ loc.xl_entry = &bucket_xh(xs->bucket)->xh_entries[xe_index];
+ xs->here = loc.xl_entry;
+ }
+
ret = ocfs2_xa_set(&loc, xi, ctxt);
if (!ret) {
xs->here = loc.xl_entry;
@@ -6100,6 +6194,22 @@ static int ocfs2_xattr_set_entry_index_block(struct inode *inode,
trace_ocfs2_xattr_set_entry_index_block(xi->xi_name);
+ /*
+ * An existing entry keeps the hash it was stored under, and that is
+ * the hash a split has to work with. A new entry is stored under the
+ * unsigned one, which is what ocfs2_xa_add_entry() will write.
+ *
+ * Read it before the set below. Filling the bucket may defragment it,
+ * and ocfs2_defrag_xattr_bucket() re-sorts the entry array, so
+ * afterwards xs->here only denotes whichever entry the sort left in
+ * that slot.
+ */
+ if (xs->not_found)
+ name_hash = ocfs2_xattr_name_hash(inode, xi->xi_name,
+ xi->xi_name_len);
+ else
+ name_hash = le32_to_cpu(xs->here->xe_name_hash);
+
ret = ocfs2_xattr_set_entry_bucket(inode, xi, xs, ctxt);
if (!ret)
goto out;
@@ -6115,17 +6225,7 @@ static int ocfs2_xattr_set_entry_index_block(struct inode *inode,
* the maximum number of collisions we will allow for then is
* one bucket's worth, so check it here whether we need to
* add a new bucket for the insert.
- *
- * An existing entry keeps the hash it was stored under, and that is
- * the hash a split has to work with. A new entry is stored under the
- * unsigned one, which is what ocfs2_xa_add_entry() will write.
*/
- if (xs->not_found)
- name_hash = ocfs2_xattr_name_hash(inode, xi->xi_name,
- xi->xi_name_len);
- else
- name_hash = le32_to_cpu(xs->here->xe_name_hash);
-
ret = ocfs2_check_xattr_bucket_collision(xs->bucket, name_hash);
if (ret) {
mlog_errno(ret);
diff --git a/kernel/panic.c b/kernel/panic.c
index 574ee5a12f92..91e48d33b7db 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -884,7 +884,7 @@ static __init int alloc_taint_buf(void)
size += 1; /* For NULL terminator */
- buf = kmalloc(size, GFP_KERNEL);
+ buf = kzalloc(size, GFP_KERNEL);
if (!buf) {
panic("Failed to allocate taint string buffer");