// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2005-2020 Junjiro R. Okajima */ /* * workqueue for asynchronous/super-io operations * todo: try new credential scheme */ #include #include "aufs.h" /* internal workqueue named AUFS_WKQ_NAME */ static struct workqueue_struct *au_wkq; struct au_wkinfo { struct work_struct wk; struct kobject *kobj; unsigned int flags; /* see wkq.h */ au_wkq_func_t func; void *args; #ifdef CONFIG_LOCKDEP int dont_check; struct held_lock **hlock; #endif struct completion *comp; }; /* ---------------------------------------------------------------------- */ /* * Aufs passes some operations to the workqueue such as the internal copyup. * This scheme looks rather unnatural for LOCKDEP debugging feature, since the * job run by workqueue depends upon the locks acquired in the other task. * Delegating a small operation to the workqueue, aufs passes its lockdep * information too. And the job in the workqueue restores the info in order to * pretend as if it acquired those locks. This is just to make LOCKDEP work * correctly and expectedly. */ #ifndef CONFIG_LOCKDEP AuStubInt0(au_wkq_lockdep_alloc, struct au_wkinfo *wkinfo); AuStubVoid(au_wkq_lockdep_free, struct au_wkinfo *wkinfo); AuStubVoid(au_wkq_lockdep_pre, struct au_wkinfo *wkinfo); AuStubVoid(au_wkq_lockdep_post, struct au_wkinfo *wkinfo); AuStubVoid(au_wkq_lockdep_init, struct au_wkinfo *wkinfo); #else static void au_wkq_lockdep_init(struct au_wkinfo *wkinfo) { wkinfo->hlock = NULL; wkinfo->dont_check = 0; } /* * 1: matched * 0: unmatched */ static int au_wkq_lockdep_test(struct lock_class_key *key, const char *name) { static DEFINE_SPINLOCK(spin); static struct { char *name; struct lock_class_key *key; } a[] = { { .name = "&sbinfo->si_rwsem" }, { .name = "&finfo->fi_rwsem" }, { .name = "&dinfo->di_rwsem" }, { .name = "&iinfo->ii_rwsem" } }; static int set; int i; /* lockless read from 'set.' see below */ if (set == ARRAY_SIZE(a)) { for (i = 0; i < ARRAY_SIZE(a); i++) if (a[i].key == key) goto match; goto unmatch; } spin_lock(&spin); if (set) for (i = 0; i < ARRAY_SIZE(a); i++) if (a[i].key == key) { spin_unlock(&spin); goto match; } for (i = 0; i < ARRAY_SIZE(a); i++) { if (a[i].key) { if (unlikely(a[i].key == key)) { /* rare but possible */ spin_unlock(&spin); goto match; } else continue; } if (strstr(a[i].name, name)) { /* * the order of these three lines is important for the * lockless read above. */ a[i].key = key; spin_unlock(&spin); set++; /* AuDbg("%d, %s\n", set, name); */ goto match; } } spin_unlock(&spin); goto unmatch; match: return 1; unmatch: return 0; } static int au_wkq_lockdep_alloc(struct au_wkinfo *wkinfo) { int err, n; struct task_struct *curr; struct held_lock **hl, *held_locks, *p; err = 0; curr = current; wkinfo->dont_check = lockdep_recursing(curr); if (wkinfo->dont_check) goto out; n = curr->lockdep_depth; if (!n) goto out; err = -ENOMEM; wkinfo->hlock = kmalloc_array(n + 1, sizeof(*wkinfo->hlock), GFP_NOFS); if (unlikely(!wkinfo->hlock)) goto out; err = 0; #if 0 /* left for debugging */ if (0 && au_debug_test()) lockdep_print_held_locks(curr); #endif held_locks = curr->held_locks; hl = wkinfo->hlock; while (n--) { p = held_locks++; if (au_wkq_lockdep_test(p->instance->key, p->instance->name)) *hl++ = p; } *hl = NULL; out: return err; } static void au_wkq_lockdep_free(struct au_wkinfo *wkinfo) { au_kfree_try_rcu(wkinfo->hlock); } static void au_wkq_lockdep_pre(struct au_wkinfo *wkinfo) { struct held_lock *p, **hl = wkinfo->hlock; int subclass; if (wkinfo->dont_check) lockdep_off(); if (!hl) return; while ((p = *hl++)) { /* assignment */ subclass = lockdep_hlock_class(p)->subclass; /* AuDbg("%s, %d\n", p->instance->name, subclass); */ if (p->read) rwsem_acquire_read(p->instance, subclass, 0, /*p->acquire_ip*/_RET_IP_); else rwsem_acquire(p->instance, subclass, 0, /*p->acquire_ip*/_RET_IP_); } } static void au_wkq_lockdep_post(struct au_wkinfo *wkinfo) { struct held_lock *p, **hl = wkinfo->hlock; if (wkinfo->dont_check) lockdep_on(); if (!hl) return; while ((p = *hl++)) /* assignment */ rwsem_release(p->instance, /*p->acquire_ip*/_RET_IP_); } #endif static void wkq_func(struct work_struct *wk) { struct au_wkinfo *wkinfo = container_of(wk, struct au_wkinfo, wk); AuDebugOn(!uid_eq(current_fsuid(), GLOBAL_ROOT_UID)); AuDebugOn(rlimit(RLIMIT_FSIZE) != RLIM_INFINITY); au_wkq_lockdep_pre(wkinfo); wkinfo->func(wkinfo->args); au_wkq_lockdep_post(wkinfo); if (au_ftest_wkq(wkinfo->flags, WAIT)) complete(wkinfo->comp); else { kobject_put(wkinfo->kobj); module_put(THIS_MODULE); /* todo: ?? */ au_kfree_rcu(wkinfo); } } /* * Since struct completion is large, try allocating it dynamically. */ #define AuWkqCompDeclare(name) struct completion *comp = NULL static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp) { *comp = kmalloc(sizeof(**comp), GFP_NOFS); if (*comp) { init_completion(*comp); wkinfo->comp = *comp; return 0; } return -ENOMEM; } static void au_wkq_comp_free(struct completion *comp) { au_kfree_rcu(comp); } static void au_wkq_run(struct au_wkinfo *wkinfo) { if (au_ftest_wkq(wkinfo->flags, NEST)) { if (au_wkq_test()) { AuWarn1("wkq from wkq, unless silly-rename on NFS," " due to a dead dir by UDBA," " or async xino write?\n"); AuDebugOn(au_ftest_wkq(wkinfo->flags, WAIT)); } } else au_dbg_verify_kthread(); if (au_ftest_wkq(wkinfo->flags, WAIT)) { INIT_WORK_ONSTACK(&wkinfo->wk, wkq_func); queue_work(au_wkq, &wkinfo->wk); } else { INIT_WORK(&wkinfo->wk, wkq_func); schedule_work(&wkinfo->wk); } } /* * Be careful. It is easy to make deadlock happen. * processA: lock, wkq and wait * processB: wkq and wait, lock in wkq * --> deadlock */ int au_wkq_do_wait(unsigned int flags, au_wkq_func_t func, void *args) { int err; AuWkqCompDeclare(comp); struct au_wkinfo wkinfo = { .flags = flags, .func = func, .args = args }; err = au_wkq_comp_alloc(&wkinfo, &comp); if (unlikely(err)) goto out; err = au_wkq_lockdep_alloc(&wkinfo); if (unlikely(err)) goto out_comp; if (!err) { au_wkq_run(&wkinfo); /* no timeout, no interrupt */ wait_for_completion(wkinfo.comp); } au_wkq_lockdep_free(&wkinfo); out_comp: au_wkq_comp_free(comp); out: destroy_work_on_stack(&wkinfo.wk); return err; } /* * Note: dget/dput() in func for aufs dentries are not supported. It will be a * problem in a concurrent umounting. */ int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb, unsigned int flags) { int err; struct au_wkinfo *wkinfo; atomic_inc(&au_sbi(sb)->si_nowait.nw_len); /* * wkq_func() must free this wkinfo. * it highly depends upon the implementation of workqueue. */ err = 0; wkinfo = kmalloc(sizeof(*wkinfo), GFP_NOFS); if (wkinfo) { wkinfo->kobj = &au_sbi(sb)->si_kobj; wkinfo->flags = flags & ~AuWkq_WAIT; wkinfo->func = func; wkinfo->args = args; wkinfo->comp = NULL; au_wkq_lockdep_init(wkinfo); kobject_get(wkinfo->kobj); __module_get(THIS_MODULE); /* todo: ?? */ au_wkq_run(wkinfo); } else { err = -ENOMEM; au_nwt_done(&au_sbi(sb)->si_nowait); } return err; } /* ---------------------------------------------------------------------- */ void au_nwt_init(struct au_nowait_tasks *nwt) { atomic_set(&nwt->nw_len, 0); /* smp_mb(); */ /* atomic_set */ init_waitqueue_head(&nwt->nw_wq); } void au_wkq_fin(void) { destroy_workqueue(au_wkq); } int __init au_wkq_init(void) { int err; err = 0; au_wkq = alloc_workqueue(AUFS_WKQ_NAME, 0, WQ_DFL_ACTIVE); if (IS_ERR(au_wkq)) err = PTR_ERR(au_wkq); else if (!au_wkq) err = -ENOMEM; return err; }