diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-30 11:00:25 +0000 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-30 11:00:25 +0000 |
| commit | f7e88613fdcbef305e9f46ccd22c362d733bb177 (patch) | |
| tree | 47cac24f4f577c1ddac0713a7117b40242e9ec5f | |
| parent | edb6c17a94809770a3632e97b295511f4112bf14 (diff) | |
| parent | 07b36d7b86ec3cd8d4a399d71db049803ab3a7f1 (diff) | |
| download | linux-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-- | .mailmap | 4 | ||||
| -rw-r--r-- | MAINTAINERS | 14 | ||||
| -rw-r--r-- | fs/fat/dir.c | 10 | ||||
| -rw-r--r-- | fs/fat/fat.h | 1 | ||||
| -rw-r--r-- | fs/fat/file.c | 70 | ||||
| -rw-r--r-- | fs/fat/inode.c | 2 | ||||
| -rw-r--r-- | fs/fat/namei_msdos.c | 9 | ||||
| -rw-r--r-- | fs/fat/namei_vfat.c | 9 | ||||
| -rw-r--r-- | fs/ocfs2/cluster/heartbeat.c | 40 | ||||
| -rw-r--r-- | fs/ocfs2/cluster/tcp.c | 5 | ||||
| -rw-r--r-- | fs/ocfs2/dlm/dlmmaster.c | 70 | ||||
| -rw-r--r-- | fs/ocfs2/dlmglue.c | 18 | ||||
| -rw-r--r-- | fs/ocfs2/journal.c | 15 | ||||
| -rw-r--r-- | fs/ocfs2/journal.h | 1 | ||||
| -rw-r--r-- | fs/ocfs2/move_extents.c | 70 | ||||
| -rw-r--r-- | fs/ocfs2/super.c | 3 | ||||
| -rw-r--r-- | fs/ocfs2/xattr.c | 146 | ||||
| -rw-r--r-- | kernel/panic.c | 2 |
18 files changed, 376 insertions, 113 deletions
@@ -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)(®->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)(®->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)(®->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)(®->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(®->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(®->hr_dev_mutex); ret = wait_event_interruptible(o2hb_steady_queue, atomic_read(®->hr_steady_iterations) == 0 || reg->hr_node_deleted); + mutex_lock(®->hr_dev_mutex); if (ret) { atomic_set(®->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(®->hr_dev_mutex); + ret = __o2hb_region_dev_store(reg, page, count); + mutex_unlock(®->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(®->hr_all_item); mutex_init(®->hr_arming_mutex); + mutex_init(®->hr_dev_mutex); reg->hr_stopping = true; INIT_DELAYED_WORK(®->hr_write_timeout_work, o2hb_write_timeout); INIT_DELAYED_WORK(®->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 = ⦥ - /* - * 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"); |
