3163 lines · 83.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* *  linux/fs/locks.c * * We implement four types of file locks: BSD locks, posix locks, open * file description locks, and leases.  For details about BSD locks, * see the flock(2) man page; for details about the other three, see * fcntl(2). * * * Locking conflicts and dependencies: * If multiple threads attempt to lock the same byte (or flock the same file) * only one can be granted the lock, and other must wait their turn. * The first lock has been "applied" or "granted", the others are "waiting" * and are "blocked" by the "applied" lock.. * * Waiting and applied locks are all kept in trees whose properties are: * *	- the root of a tree may be an applied or waiting lock. *	- every other node in the tree is a waiting lock that *	  conflicts with every ancestor of that node. * * Every such tree begins life as a waiting singleton which obviously * satisfies the above properties. * * The only ways we modify trees preserve these properties: * *	1. We may add a new leaf node, but only after first verifying that it *	   conflicts with all of its ancestors. *	2. We may remove the root of a tree, creating a new singleton *	   tree from the root and N new trees rooted in the immediate *	   children. *	3. If the root of a tree is not currently an applied lock, we may *	   apply it (if possible). *	4. We may upgrade the root of the tree (either extend its range, *	   or upgrade its entire range from read to write). * * When an applied lock is modified in a way that reduces or downgrades any * part of its range, we remove all its children (2 above).  This particularly * happens when a lock is unlocked. * * For each of those child trees we "wake up" the thread which is * waiting for the lock so it can continue handling as follows: if the * root of the tree applies, we do so (3).  If it doesn't, it must * conflict with some applied lock.  We remove (wake up) all of its children * (2), and add it is a new leaf to the tree rooted in the applied * lock (1).  We then repeat the process recursively with those * children. * */#include <linux/capability.h>#include <linux/file.h>#include <linux/fdtable.h>#include <linux/filelock.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/security.h>#include <linux/slab.h>#include <linux/syscalls.h>#include <linux/time.h>#include <linux/rcupdate.h>#include <linux/pid_namespace.h>#include <linux/hashtable.h>#include <linux/percpu.h>#include <linux/sysctl.h> #define CREATE_TRACE_POINTS#include <trace/events/filelock.h> #include <linux/uaccess.h> static struct file_lock *file_lock(struct file_lock_core *flc){	return container_of(flc, struct file_lock, c);} static struct file_lease *file_lease(struct file_lock_core *flc){	return container_of(flc, struct file_lease, c);} static bool lease_breaking(struct file_lease *fl){	return fl->c.flc_flags & (FL_UNLOCK_PENDING | FL_DOWNGRADE_PENDING);} static int target_leasetype(struct file_lease *fl){	if (fl->c.flc_flags & FL_UNLOCK_PENDING)		return F_UNLCK;	if (fl->c.flc_flags & FL_DOWNGRADE_PENDING)		return F_RDLCK;	return fl->c.flc_type;} static int leases_enable = 1;static int lease_break_time = 45; #ifdef CONFIG_SYSCTLstatic const struct ctl_table locks_sysctls[] = {	{		.procname	= "leases-enable",		.data		= &leases_enable,		.maxlen		= sizeof(int),		.mode		= 0644,		.proc_handler	= proc_dointvec,	},#ifdef CONFIG_MMU	{		.procname	= "lease-break-time",		.data		= &lease_break_time,		.maxlen		= sizeof(int),		.mode		= 0644,		.proc_handler	= proc_dointvec,	},#endif /* CONFIG_MMU */}; static int __init init_fs_locks_sysctls(void){	register_sysctl_init("fs", locks_sysctls);	return 0;}early_initcall(init_fs_locks_sysctls);#endif /* CONFIG_SYSCTL */ /* * The global file_lock_list is only used for displaying /proc/locks, so we * keep a list on each CPU, with each list protected by its own spinlock. * Global serialization is done using file_rwsem. * * Note that alterations to the list also require that the relevant flc_lock is * held. */struct file_lock_list_struct {	spinlock_t		lock;	struct hlist_head	hlist;};static DEFINE_PER_CPU(struct file_lock_list_struct, file_lock_list);DEFINE_STATIC_PERCPU_RWSEM(file_rwsem);  /* * The blocked_hash is used to find POSIX lock loops for deadlock detection. * It is protected by blocked_lock_lock. * * We hash locks by lockowner in order to optimize searching for the lock a * particular lockowner is waiting on. * * FIXME: make this value scale via some heuristic? We generally will want more * buckets when we have more lockowners holding locks, but that's a little * difficult to determine without knowing what the workload will look like. */#define BLOCKED_HASH_BITS	7static DEFINE_HASHTABLE(blocked_hash, BLOCKED_HASH_BITS); /* * This lock protects the blocked_hash. Generally, if you're accessing it, you * want to be holding this lock. * * In addition, it also protects the fl->fl_blocked_requests list, and the * fl->fl_blocker pointer for file_lock structures that are acting as lock * requests (in contrast to those that are acting as records of acquired locks). * * Note that when we acquire this lock in order to change the above fields, * we often hold the flc_lock as well. In certain cases, when reading the fields * protected by this lock, we can skip acquiring it iff we already hold the * flc_lock. */static DEFINE_SPINLOCK(blocked_lock_lock); static struct kmem_cache *flctx_cache __ro_after_init;static struct kmem_cache *filelock_cache __ro_after_init;static struct kmem_cache *filelease_cache __ro_after_init; static struct file_lock_context *locks_get_lock_context(struct inode *inode, int type){	struct file_lock_context *ctx; 	ctx = locks_inode_context(inode);	if (likely(ctx) || type == F_UNLCK)		goto out; 	ctx = kmem_cache_alloc(flctx_cache, GFP_KERNEL);	if (!ctx)		goto out; 	spin_lock_init(&ctx->flc_lock);	INIT_LIST_HEAD(&ctx->flc_flock);	INIT_LIST_HEAD(&ctx->flc_posix);	INIT_LIST_HEAD(&ctx->flc_lease); 	/*	 * Assign the pointer if it's not already assigned. If it is, then	 * free the context we just allocated.	 */	spin_lock(&inode->i_lock);	if (!(inode->i_opflags & IOP_FLCTX)) {		VFS_BUG_ON_INODE(inode->i_flctx, inode);		WRITE_ONCE(inode->i_flctx, ctx);		/*		 * Paired with locks_inode_context().		 */		smp_store_release(&inode->i_opflags, inode->i_opflags | IOP_FLCTX);		spin_unlock(&inode->i_lock);	} else {		VFS_BUG_ON_INODE(!inode->i_flctx, inode);		spin_unlock(&inode->i_lock);		kmem_cache_free(flctx_cache, ctx);		ctx = locks_inode_context(inode);	}out:	trace_locks_get_lock_context(inode, type, ctx);	return ctx;} static voidlocks_dump_ctx_list(struct list_head *list, char *list_type){	struct file_lock_core *flc; 	list_for_each_entry(flc, list, flc_list)		pr_warn("%s: fl_owner=%p fl_flags=0x%x fl_type=0x%x fl_pid=%u\n",			list_type, flc->flc_owner, flc->flc_flags,			flc->flc_type, flc->flc_pid);} static voidlocks_check_ctx_lists(struct inode *inode){	struct file_lock_context *ctx = inode->i_flctx; 	if (unlikely(!list_empty(&ctx->flc_flock) ||		     !list_empty(&ctx->flc_posix) ||		     !list_empty(&ctx->flc_lease))) {		pr_warn("Leaked locks on dev=0x%x:0x%x ino=0x%llx:\n",			MAJOR(inode->i_sb->s_dev), MINOR(inode->i_sb->s_dev),			inode->i_ino);		locks_dump_ctx_list(&ctx->flc_flock, "FLOCK");		locks_dump_ctx_list(&ctx->flc_posix, "POSIX");		locks_dump_ctx_list(&ctx->flc_lease, "LEASE");	}} static voidlocks_check_ctx_file_list(struct file *filp, struct list_head *list, char *list_type){	struct file_lock_core *flc;	struct inode *inode = file_inode(filp); 	list_for_each_entry(flc, list, flc_list)		if (flc->flc_file == filp)			pr_warn("Leaked %s lock on dev=0x%x:0x%x ino=0x%llx "				" fl_owner=%p fl_flags=0x%x fl_type=0x%x fl_pid=%u\n",				list_type, MAJOR(inode->i_sb->s_dev),				MINOR(inode->i_sb->s_dev), inode->i_ino,				flc->flc_owner, flc->flc_flags,				flc->flc_type, flc->flc_pid);} voidlocks_free_lock_context(struct inode *inode){	struct file_lock_context *ctx = locks_inode_context(inode); 	if (unlikely(ctx)) {		locks_check_ctx_lists(inode);		kmem_cache_free(flctx_cache, ctx);	}} static void locks_init_lock_heads(struct file_lock_core *flc){	INIT_HLIST_NODE(&flc->flc_link);	INIT_LIST_HEAD(&flc->flc_list);	INIT_LIST_HEAD(&flc->flc_blocked_requests);	INIT_LIST_HEAD(&flc->flc_blocked_member);	init_waitqueue_head(&flc->flc_wait);} /* Allocate an empty lock structure. */struct file_lock *locks_alloc_lock(void){	struct file_lock *fl = kmem_cache_zalloc(filelock_cache, GFP_KERNEL); 	if (fl)		locks_init_lock_heads(&fl->c); 	return fl;}EXPORT_SYMBOL_GPL(locks_alloc_lock); /* Allocate an empty lock structure. */struct file_lease *locks_alloc_lease(void){	struct file_lease *fl = kmem_cache_zalloc(filelease_cache, GFP_KERNEL); 	if (fl)		locks_init_lock_heads(&fl->c); 	return fl;}EXPORT_SYMBOL_GPL(locks_alloc_lease); void locks_release_private(struct file_lock *fl){	struct file_lock_core *flc = &fl->c; 	BUG_ON(waitqueue_active(&flc->flc_wait));	BUG_ON(!list_empty(&flc->flc_list));	BUG_ON(!list_empty(&flc->flc_blocked_requests));	BUG_ON(!list_empty(&flc->flc_blocked_member));	BUG_ON(!hlist_unhashed(&flc->flc_link)); 	if (fl->fl_ops) {		if (fl->fl_ops->fl_release_private)			fl->fl_ops->fl_release_private(fl);		fl->fl_ops = NULL;	} 	if (fl->fl_lmops) {		if (fl->fl_lmops->lm_put_owner) {			fl->fl_lmops->lm_put_owner(flc->flc_owner);			flc->flc_owner = NULL;		}		fl->fl_lmops = NULL;	}}EXPORT_SYMBOL_GPL(locks_release_private); /** * locks_owner_has_blockers - Check for blocking lock requests * @flctx: file lock context * @owner: lock owner * * Return values: *   %true: @owner has at least one blocker *   %false: @owner has no blockers */bool locks_owner_has_blockers(struct file_lock_context *flctx, fl_owner_t owner){	struct file_lock_core *flc; 	spin_lock(&flctx->flc_lock);	list_for_each_entry(flc, &flctx->flc_posix, flc_list) {		if (flc->flc_owner != owner)			continue;		if (!list_empty(&flc->flc_blocked_requests)) {			spin_unlock(&flctx->flc_lock);			return true;		}	}	spin_unlock(&flctx->flc_lock);	return false;}EXPORT_SYMBOL_GPL(locks_owner_has_blockers); /* Free a lock which is not in use. */void locks_free_lock(struct file_lock *fl){	locks_release_private(fl);	kmem_cache_free(filelock_cache, fl);}EXPORT_SYMBOL(locks_free_lock); /* Free a lease which is not in use. */void locks_free_lease(struct file_lease *fl){	kmem_cache_free(filelease_cache, fl);}EXPORT_SYMBOL(locks_free_lease); static voidlocks_dispose_list(struct list_head *dispose){	struct file_lock_core *flc; 	while (!list_empty(dispose)) {		flc = list_first_entry(dispose, struct file_lock_core, flc_list);		list_del_init(&flc->flc_list);		locks_free_lock(file_lock(flc));	}} static voidlease_dispose_list(struct list_head *dispose){	struct file_lock_core *flc; 	while (!list_empty(dispose)) {		flc = list_first_entry(dispose, struct file_lock_core, flc_list);		list_del_init(&flc->flc_list);		locks_free_lease(file_lease(flc));	}} void locks_init_lock(struct file_lock *fl){	memset(fl, 0, sizeof(struct file_lock));	locks_init_lock_heads(&fl->c);}EXPORT_SYMBOL(locks_init_lock); void locks_init_lease(struct file_lease *fl){	memset(fl, 0, sizeof(*fl));	locks_init_lock_heads(&fl->c);}EXPORT_SYMBOL(locks_init_lease); /* * Initialize a new lock from an existing file_lock structure. */void locks_copy_conflock(struct file_lock *new, struct file_lock *fl){	new->c.flc_owner = fl->c.flc_owner;	new->c.flc_pid = fl->c.flc_pid;	new->c.flc_file = NULL;	new->c.flc_flags = fl->c.flc_flags;	new->c.flc_type = fl->c.flc_type;	new->fl_start = fl->fl_start;	new->fl_end = fl->fl_end;	new->fl_lmops = fl->fl_lmops;	new->fl_ops = NULL; 	if (fl->fl_lmops) {		if (fl->fl_lmops->lm_get_owner)			fl->fl_lmops->lm_get_owner(fl->c.flc_owner);	}}EXPORT_SYMBOL(locks_copy_conflock); void locks_copy_lock(struct file_lock *new, struct file_lock *fl){	/* "new" must be a freshly-initialized lock */	WARN_ON_ONCE(new->fl_ops); 	locks_copy_conflock(new, fl); 	new->c.flc_file = fl->c.flc_file;	new->fl_ops = fl->fl_ops; 	if (fl->fl_ops) {		if (fl->fl_ops->fl_copy_lock)			fl->fl_ops->fl_copy_lock(new, fl);	}}EXPORT_SYMBOL(locks_copy_lock); static void locks_move_blocks(struct file_lock *new, struct file_lock *fl){	struct file_lock *f; 	/*	 * As ctx->flc_lock is held, new requests cannot be added to	 * ->flc_blocked_requests, so we don't need a lock to check if it	 * is empty.	 */	if (list_empty(&fl->c.flc_blocked_requests))		return;	spin_lock(&blocked_lock_lock);	list_splice_init(&fl->c.flc_blocked_requests,			 &new->c.flc_blocked_requests);	list_for_each_entry(f, &new->c.flc_blocked_requests,			    c.flc_blocked_member)		f->c.flc_blocker = &new->c;	spin_unlock(&blocked_lock_lock);} static inline int flock_translate_cmd(int cmd) {	switch (cmd) {	case LOCK_SH:		return F_RDLCK;	case LOCK_EX:		return F_WRLCK;	case LOCK_UN:		return F_UNLCK;	}	return -EINVAL;} /* Fill in a file_lock structure with an appropriate FLOCK lock. */static void flock_make_lock(struct file *filp, struct file_lock *fl, int type){	locks_init_lock(fl); 	fl->c.flc_file = filp;	fl->c.flc_owner = filp;	fl->c.flc_pid = current->tgid;	fl->c.flc_flags = FL_FLOCK;	fl->c.flc_type = type;	fl->fl_end = OFFSET_MAX;} static int assign_type(struct file_lock_core *flc, int type){	switch (type) {	case F_RDLCK:	case F_WRLCK:

Lines 1–500 of 3163.