#include "opt_debug_lockf.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/proc.h>
#include <sys/unistd.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/fcntl.h>
#include <sys/resourcevar.h>
#include <sys/lockf.h>
#include <machine/limits.h>
#include <machine/stdarg.h>
#include <sys/spinlock2.h>
struct lf_pcpu {
struct lockf_range *free1;
struct lockf_range *free2;
} __cachealign;
static struct lf_pcpu *lf_pcpu_array;
#ifdef LOCKF_DEBUG
int lf_print_ranges = 0;
static void _lf_print_lock(const struct lockf *);
static void _lf_printf(const char *, ...) __printflike(1, 2);
#define lf_print_lock(lock) if (lf_print_ranges) _lf_print_lock(lock)
#define lf_printf(ctl, args...) if (lf_print_ranges) _lf_printf(ctl, args)
#else
#define lf_print_lock(lock)
#define lf_printf(ctl, args...)
#endif
static MALLOC_DEFINE(M_LOCKF, "lockf", "Byte-range locking structures");
static void lf_wakeup(struct lockf *, off_t, off_t);
static struct lockf_range *lf_alloc_range(void);
static void lf_create_range(struct lockf_range *, struct proc *, int, int,
off_t, off_t);
static void lf_insert(struct lockf_range_list *list,
struct lockf_range *elm,
struct lockf_range *insert_point);
static void lf_destroy_range(struct lockf_range *);
static int lf_setlock(struct lockf *, struct proc *, int, int,
off_t, off_t);
static int lf_getlock(struct flock *, struct lockf *, struct proc *,
int, int, off_t, off_t);
static int lf_count_change(struct proc *, int);
static __inline
int
lf_match(struct lockf_range *range, int type, int flags)
{
if (range->lf_type != type)
return(0);
if ((range->lf_flags ^ flags) & F_POSIX)
return(0);
return(1);
}
static __inline
int
lf_overlap(const struct lockf_range *range, off_t start, off_t end)
{
if (range->lf_start >= start && range->lf_start <= end)
return(1);
else if (start >= range->lf_start && start <= range->lf_end)
return(1);
else
return(0);
}
void
lf_count_adjust(struct proc *p, int increase)
{
struct uidinfo *uip;
struct uidcount *pup;
int n;
KKASSERT(p != NULL);
uip = p->p_ucred->cr_uidinfo;
pup = &uip->ui_pcpu[mycpuid];
if (increase) {
for (n = 0; n < ncpus; ++n)
pup->pu_posixlocks += p->p_uidpcpu[n].pu_posixlocks;
} else {
for (n = 0; n < ncpus; ++n)
pup->pu_posixlocks -= p->p_uidpcpu[n].pu_posixlocks;
}
if (pup->pu_posixlocks < -PUP_LIMIT ||
pup->pu_posixlocks > PUP_LIMIT) {
atomic_add_int(&uip->ui_posixlocks, pup->pu_posixlocks);
pup->pu_posixlocks = 0;
}
}
static int
lf_count_change(struct proc *owner, int diff)
{
struct uidinfo *uip;
int max, ret;
if (owner == NULL)
return(0);
uip = owner->p_ucred->cr_uidinfo;
max = MIN(owner->p_rlimit[RLIMIT_POSIXLOCKS].rlim_cur,
maxposixlocksperuid);
if (diff > 0 && owner->p_ucred->cr_uid != 0 && max != -1 &&
uip->ui_posixlocks >= max ) {
ret = 1;
} else {
struct uidcount *pup;
int cpu = mycpuid;
pup = &uip->ui_pcpu[cpu];
pup->pu_posixlocks += diff;
if (pup->pu_posixlocks < -PUP_LIMIT ||
pup->pu_posixlocks > PUP_LIMIT) {
atomic_add_int(&uip->ui_posixlocks, pup->pu_posixlocks);
pup->pu_posixlocks = 0;
}
owner->p_uidpcpu[cpu].pu_posixlocks += diff;
ret = 0;
}
return ret;
}
int
lf_advlock(struct vop_advlock_args *ap, struct lockf *lock, u_quad_t size)
{
struct flock *fl = ap->a_fl;
struct proc *owner;
off_t start, end;
int type, flags, error;
lwkt_token_t token;
switch (fl->l_whence) {
case SEEK_SET:
case SEEK_CUR:
start = fl->l_start;
break;
case SEEK_END:
start = size + fl->l_start;
break;
default:
return(EINVAL);
}
flags = ap->a_flags;
if (start < 0)
return(EINVAL);
if (fl->l_len == 0) {
flags |= F_NOEND;
end = LLONG_MAX;
} else if (fl->l_len < 0) {
return(EINVAL);
} else {
end = start + fl->l_len - 1;
if (end < start)
return(EINVAL);
}
type = fl->l_type;
owner = (struct proc *)ap->a_id;
token = lwkt_getpooltoken(lock);
if (lock->init_done == 0) {
TAILQ_INIT(&lock->lf_range);
TAILQ_INIT(&lock->lf_blocked);
lock->init_done = 1;
}
switch(ap->a_op) {
case F_SETLK:
error = lf_setlock(lock, owner, type, flags, start, end);
if ((ap->a_vp->v_flag & VMAYHAVELOCKS) == 0)
vsetflags(ap->a_vp, VMAYHAVELOCKS);
break;
case F_UNLCK:
error = lf_setlock(lock, owner, type, flags, start, end);
#if 0
if (TAILQ_EMPTY(&lock->lf_range) &&
TAILQ_EMPTY(&lock->lf_blocked)) {
vclrflags(ap->a_vp, VMAYHAVELOCKS);
}
#endif
break;
case F_GETLK:
error = lf_getlock(fl, lock, owner, type, flags, start, end);
break;
default:
error = EINVAL;
break;
}
lwkt_reltoken(token);
return(error);
}
static int
lf_setlock(struct lockf *lock, struct proc *owner, int type, int flags,
off_t start, off_t end)
{
struct lockf_range *range;
struct lockf_range *brange;
struct lockf_range *next;
struct lockf_range *first_match;
struct lockf_range *last_match;
struct lockf_range *insert_point;
struct lockf_range *new_range1;
struct lockf_range *new_range2;
int wakeup_needed;
int double_clip;
int unlock_override;
int error = 0;
int count;
struct lockf_range_list deadlist;
new_range1 = NULL;
new_range2 = NULL;
count = 0;
restart:
if (new_range1 == NULL)
new_range1 = lf_alloc_range();
if (new_range2 == NULL)
new_range2 = lf_alloc_range();
first_match = NULL;
last_match = NULL;
insert_point = NULL;
wakeup_needed = 0;
lf_print_lock(lock);
TAILQ_FOREACH(range, &lock->lf_range, lf_link) {
if (insert_point == NULL && range->lf_start >= start)
insert_point = range;
if (range->lf_end < start)
continue;
if (range->lf_start > end) {
range = NULL;
break;
}
if (range->lf_owner == owner) {
if (first_match == NULL)
first_match = range;
last_match = range;
continue;
}
if (type != F_UNLCK) {
if (type == F_WRLCK || range->lf_type == F_WRLCK)
break;
}
}
if (range != NULL) {
if ((flags & F_WAIT) == 0) {
error = EAGAIN;
goto do_cleanup;
}
if (flags & F_POSIX) {
TAILQ_FOREACH(brange, &lock->lf_blocked, lf_link) {
if (brange->lf_owner == range->lf_owner) {
error = EDEADLK;
goto do_cleanup;
}
}
}
if ((flags & F_POSIX) == 0 && type == F_WRLCK)
lf_setlock(lock, owner, F_UNLCK, 0, start, end);
brange = new_range1;
new_range1 = NULL;
lf_create_range(brange, owner, type, 0, start, end);
TAILQ_INSERT_TAIL(&lock->lf_blocked, brange, lf_link);
error = tsleep(brange, PCATCH, "lockf", 0);
if (brange->lf_flags == 0)
TAILQ_REMOVE(&lock->lf_blocked, brange, lf_link);
if (error == 0 && count == 2)
tsleep(brange, 0, "lockfz", 2);
else
++count;
lf_destroy_range(brange);
if (error)
goto do_cleanup;
goto restart;
}
if (first_match == NULL) {
if (type == F_UNLCK)
goto do_wakeup;
if (flags & F_POSIX) {
if (lf_count_change(owner, 1)) {
error = ENOLCK;
goto do_cleanup;
}
}
range = new_range1;
new_range1 = NULL;
lf_create_range(range, owner, type, flags, start, end);
lf_insert(&lock->lf_range, range, insert_point);
goto do_wakeup;
}
double_clip = 0;
unlock_override = 0;
if (first_match->lf_start < start) {
if (first_match == last_match && last_match->lf_end > end)
double_clip = 1;
} else if (type == F_UNLCK && last_match->lf_end <= end) {
unlock_override = 1;
}
count = 0;
if ((flags & F_POSIX) && !unlock_override) {
if (!lf_match(first_match, type, flags) &&
!lf_match(last_match, type, flags)
) {
if (double_clip && type != F_UNLCK)
count = -2;
else
count = -1;
}
if (count && lf_count_change(owner, -count)) {
error = ENOLCK;
goto do_cleanup;
}
}
if (type != F_UNLCK) {
brange = new_range1;
new_range1 = NULL;
lf_create_range(brange, owner, type, flags, start, end);
lf_insert(&lock->lf_range, brange, insert_point);
insert_point = brange;
if (flags & F_POSIX)
++count;
} else {
brange = NULL;
}
if (double_clip) {
KKASSERT(first_match == last_match);
last_match = new_range2;
new_range2 = NULL;
lf_create_range(last_match, first_match->lf_owner,
first_match->lf_type, first_match->lf_flags,
end + 1, first_match->lf_end);
first_match->lf_end = start - 1;
first_match->lf_flags &= ~F_NOEND;
lf_insert(&lock->lf_range, last_match, first_match);
if (last_match->lf_flags & F_POSIX)
++count;
}
TAILQ_INIT(&deadlist);
next = first_match;
while ((range = next) != NULL) {
next = TAILQ_NEXT(range, lf_link);
if (range->lf_owner != owner || range == brange)
continue;
if (type == F_UNLCK)
wakeup_needed = 1;
if (type == F_RDLCK && range->lf_type == F_WRLCK)
wakeup_needed = 1;
if (range->lf_start < start) {
KKASSERT(range == first_match);
if (brange &&
range->lf_end >= start - 1 &&
lf_match(range, type, flags)) {
range->lf_end = brange->lf_end;
range->lf_flags |= brange->lf_flags & F_NOEND;
if (next == brange)
next = TAILQ_NEXT(next, lf_link);
TAILQ_REMOVE(&lock->lf_range, brange, lf_link);
if (brange->lf_flags & F_POSIX)
--count;
TAILQ_INSERT_TAIL(&deadlist, brange, lf_link);
brange = range;
} else if (range->lf_end >= start) {
range->lf_end = start - 1;
if (type != F_UNLCK)
range->lf_flags &= ~F_NOEND;
}
if (range == last_match)
break;
continue;
}
if (range->lf_end > end) {
KKASSERT(range == last_match);
if (brange &&
range->lf_start <= end + 1 &&
lf_match(range, type, flags)) {
brange->lf_end = range->lf_end;
brange->lf_flags |= range->lf_flags & F_NOEND;
TAILQ_REMOVE(&lock->lf_range, range, lf_link);
if (range->lf_flags & F_POSIX)
--count;
TAILQ_INSERT_TAIL(&deadlist, range, lf_link);
} else if (range->lf_start <= end) {
range->lf_start = end + 1;
TAILQ_REMOVE(&lock->lf_range, range, lf_link);
lf_insert(&lock->lf_range, range, next);
}
break;
}
KKASSERT(range->lf_start >= start && range->lf_end <= end);
TAILQ_REMOVE(&lock->lf_range, range, lf_link);
if (range->lf_flags & F_POSIX)
--count;
TAILQ_INSERT_TAIL(&deadlist, range, lf_link);
if (range == last_match)
break;
}
if (brange) {
range = TAILQ_PREV(brange, lockf_range_list, lf_link);
if (range &&
range->lf_owner == owner &&
range->lf_end == brange->lf_start - 1 &&
lf_match(range, type, flags)
) {
range->lf_end = brange->lf_end;
range->lf_flags |= brange->lf_flags & F_NOEND;
TAILQ_REMOVE(&lock->lf_range, brange, lf_link);
if (brange->lf_flags & F_POSIX)
--count;
TAILQ_INSERT_TAIL(&deadlist, brange, lf_link);
brange = range;
}
range = TAILQ_NEXT(brange, lf_link);
if (range &&
range->lf_owner == owner &&
range->lf_start == brange->lf_end + 1 &&
lf_match(range, type, flags)
) {
brange->lf_end = range->lf_end;
brange->lf_flags |= range->lf_flags & F_NOEND;
TAILQ_REMOVE(&lock->lf_range, range, lf_link);
if (range->lf_flags & F_POSIX)
--count;
TAILQ_INSERT_TAIL(&deadlist, range, lf_link);
}
}
while ((range = TAILQ_FIRST(&deadlist)) != NULL) {
TAILQ_REMOVE(&deadlist, range, lf_link);
lf_destroy_range(range);
}
KKASSERT(count <= 0);
if (count < 0)
lf_count_change(owner, count);
do_wakeup:
lf_print_lock(lock);
if (wakeup_needed)
lf_wakeup(lock, start, end);
error = 0;
do_cleanup:
if (new_range1 != NULL)
lf_destroy_range(new_range1);
if (new_range2 != NULL)
lf_destroy_range(new_range2);
return(error);
}
static int
lf_getlock(struct flock *fl, struct lockf *lock, struct proc *owner,
int type, int flags, off_t start, off_t end)
{
struct lockf_range *range;
TAILQ_FOREACH(range, &lock->lf_range, lf_link)
if (range->lf_owner != owner &&
lf_overlap(range, start, end) &&
(type == F_WRLCK || range->lf_type == F_WRLCK))
break;
if (range == NULL) {
fl->l_type = F_UNLCK;
return(0);
}
fl->l_type = range->lf_type;
fl->l_whence = SEEK_SET;
fl->l_start = range->lf_start;
if (range->lf_flags & F_NOEND)
fl->l_len = 0;
else
fl->l_len = range->lf_end - range->lf_start + 1;
if (range->lf_owner != NULL && (range->lf_flags & F_POSIX))
fl->l_pid = range->lf_owner->p_pid;
else
fl->l_pid = -1;
return(0);
}
static void
lf_wakeup(struct lockf *lock, off_t start, off_t end)
{
struct lockf_range *range, *nrange;
TAILQ_FOREACH_MUTABLE(range, &lock->lf_blocked, lf_link, nrange) {
if (lf_overlap(range, start, end) == 0)
continue;
TAILQ_REMOVE(&lock->lf_blocked, range, lf_link);
range->lf_flags = 1;
wakeup(range);
}
}
static struct lockf_range *
lf_alloc_range(void)
{
struct lockf_range *range;
struct lf_pcpu *lfpc;
lfpc = &lf_pcpu_array[mycpuid];
if ((range = lfpc->free1) != NULL) {
lfpc->free1 = NULL;
return range;
}
if ((range = lfpc->free2) != NULL) {
lfpc->free2 = NULL;
return range;
}
range = kmalloc(sizeof(struct lockf_range), M_LOCKF, M_WAITOK);
range->lf_owner = NULL;
return(range);
}
static void
lf_insert(struct lockf_range_list *list, struct lockf_range *elm,
struct lockf_range *insert_point)
{
while (insert_point && insert_point->lf_start < elm->lf_start)
insert_point = TAILQ_NEXT(insert_point, lf_link);
if (insert_point != NULL)
TAILQ_INSERT_BEFORE(insert_point, elm, lf_link);
else
TAILQ_INSERT_TAIL(list, elm, lf_link);
}
static void
lf_create_range(struct lockf_range *range, struct proc *owner, int type,
int flags, off_t start, off_t end)
{
KKASSERT(start <= end);
range->lf_type = type;
range->lf_flags = flags;
range->lf_start = start;
range->lf_end = end;
range->lf_owner = owner;
lf_printf("lf_create_range: %ju..%ju\n",
(uintmax_t)range->lf_start, (uintmax_t)range->lf_end);
}
static void
lf_destroy_range(struct lockf_range *range)
{
struct lf_pcpu *lfpc;
lf_printf("lf_destroy_range: %ju..%ju\n",
(uintmax_t)range->lf_start, (uintmax_t)range->lf_end);
lfpc = &lf_pcpu_array[mycpuid];
if (lfpc->free1 == NULL) {
range->lf_owner = NULL;
lfpc->free1 = range;
return;
}
if (lfpc->free2 == NULL) {
range->lf_owner = NULL;
lfpc->free2 = range;
return;
}
kfree(range, M_LOCKF);
}
#ifdef LOCKF_DEBUG
static void
_lf_printf(const char *ctl, ...)
{
struct proc *p;
__va_list va;
if (lf_print_ranges) {
if ((p = curproc) != NULL)
kprintf("pid %d (%s): ", p->p_pid, p->p_comm);
}
__va_start(va, ctl);
kvprintf(ctl, va);
__va_end(va);
}
static void
_lf_print_lock(const struct lockf *lock)
{
struct lockf_range *range;
if (lf_print_ranges == 0)
return;
if (TAILQ_EMPTY(&lock->lf_range)) {
lf_printf("lockf %p: no ranges locked\n", lock);
} else {
lf_printf("lockf %p:\n", lock);
}
TAILQ_FOREACH(range, &lock->lf_range, lf_link)
kprintf("\t%jd..%jd type %s owned by %d\n",
(uintmax_t)range->lf_start, (uintmax_t)range->lf_end,
range->lf_type == F_RDLCK ? "shared" : "exclusive",
range->lf_flags & F_POSIX ? range->lf_owner->p_pid : -1);
if (TAILQ_EMPTY(&lock->lf_blocked))
kprintf("no process waiting for range\n");
else
kprintf("blocked locks:");
TAILQ_FOREACH(range, &lock->lf_blocked, lf_link)
kprintf("\t%jd..%jd type %s waiting on %p\n",
(uintmax_t)range->lf_start, (uintmax_t)range->lf_end,
range->lf_type == F_RDLCK ? "shared" : "exclusive",
range);
}
#endif
static void
lf_init(void *dummy __unused)
{
lf_pcpu_array = kmalloc(sizeof(*lf_pcpu_array) * ncpus,
M_LOCKF, M_WAITOK | M_ZERO);
}
SYSINIT(lockf, SI_BOOT2_MACHDEP, SI_ORDER_ANY, lf_init, NULL);