opinfo->fid = id;
opinfo->Tid = Tid;
INIT_LIST_HEAD(&opinfo->op_entry);
+ INIT_LIST_HEAD(&opinfo->lease_entry);
init_waitqueue_head(&opinfo->oplock_q);
init_waitqueue_head(&opinfo->oplock_brk);
atomic_set(&opinfo->refcount, 1);
return opinfo;
}
-static void lease_add_list(struct oplock_info *opinfo)
+static void lease_get(struct lease *lease)
+{
+ atomic_inc(&lease->refcount);
+}
+
+static void lease_put(struct lease *lease)
{
- struct lease_table *lb = opinfo->o_lease->l_lb;
+ if (lease && atomic_dec_and_test(&lease->refcount))
+ kfree(lease);
+}
+static void lease_add_table(struct lease *lease, struct lease_table *lb)
+{
+ lease_get(lease);
+ lease->l_lb = lb;
spin_lock(&lb->lb_lock);
- list_add_rcu(&opinfo->lease_entry, &lb->lease_list);
+ list_add_rcu(&lease->l_entry, &lb->lease_list);
spin_unlock(&lb->lb_lock);
}
-static void lease_del_list(struct oplock_info *opinfo)
+static void lease_del_table(struct lease *lease)
{
- struct lease_table *lb = opinfo->o_lease->l_lb;
+ struct lease_table *lb = lease->l_lb;
if (!lb)
return;
spin_lock(&lb->lb_lock);
- if (list_empty(&opinfo->lease_entry)) {
+ if (list_empty(&lease->l_entry)) {
spin_unlock(&lb->lb_lock);
return;
}
- list_del_init(&opinfo->lease_entry);
- opinfo->o_lease->l_lb = NULL;
+ list_del_init(&lease->l_entry);
+ lease->l_lb = NULL;
spin_unlock(&lb->lb_lock);
+
+ lease_put(lease);
}
static struct lease_table *alloc_lease_table(struct oplock_info *opinfo)
return lb;
}
-static int alloc_lease(struct oplock_info *opinfo, struct lease_ctx_info *lctx)
+static struct lease *alloc_lease(struct lease_ctx_info *lctx,
+ struct ksmbd_inode *ci)
{
struct lease *lease;
lease = kmalloc_obj(struct lease, KSMBD_DEFAULT_GFP);
if (!lease)
- return -ENOMEM;
+ return NULL;
memcpy(lease->lease_key, lctx->lease_key, SMB2_LEASE_KEY_SIZE);
lease->state = lctx->req_state;
memcpy(lease->parent_lease_key, lctx->parent_lease_key, SMB2_LEASE_KEY_SIZE);
lease->version = lctx->version;
lease->epoch = lctx->version == 2 ? 1 : 0;
- INIT_LIST_HEAD(&opinfo->lease_entry);
- opinfo->o_lease = lease;
+ lease->ci = ci;
+ lease->l_lb = NULL;
+ INIT_LIST_HEAD(&lease->l_entry);
+ INIT_LIST_HEAD(&lease->open_list);
+ spin_lock_init(&lease->lock);
+ atomic_set(&lease->refcount, 1);
+
+ return lease;
+}
- return 0;
+static void lease_add_open(struct lease *lease, struct oplock_info *opinfo)
+{
+ spin_lock(&lease->lock);
+ list_add(&opinfo->lease_entry, &lease->open_list);
+ spin_unlock(&lease->lock);
}
-static void free_lease(struct oplock_info *opinfo)
+static void lease_del_open(struct oplock_info *opinfo)
{
- struct lease *lease;
+ struct lease *lease = opinfo->o_lease;
+ bool remove_table = false;
+
+ if (!lease)
+ return;
+
+ spin_lock(&lease->lock);
+ if (!list_empty(&opinfo->lease_entry)) {
+ list_del_init(&opinfo->lease_entry);
+ remove_table = list_empty(&lease->open_list);
+ }
+ spin_unlock(&lease->lock);
+
+ if (remove_table) {
+ write_lock(&lease_list_lock);
+ lease_del_table(lease);
+ write_unlock(&lease_list_lock);
+ }
+}
- lease = opinfo->o_lease;
- kfree(lease);
+static void free_lease(struct oplock_info *opinfo)
+{
+ lease_put(opinfo->o_lease);
}
static void __free_opinfo(struct oplock_info *opinfo)
call_rcu(&opinfo->rcu, free_opinfo_rcu);
}
+void lease_update_oplock_levels(struct lease *lease)
+{
+ struct oplock_info *opinfo;
+ __u8 level;
+
+ if (!lease)
+ return;
+
+ level = smb2_map_lease_to_oplock(lease->state);
+ spin_lock(&lease->lock);
+ list_for_each_entry(opinfo, &lease->open_list, lease_entry)
+ opinfo->level = level;
+ spin_unlock(&lease->lock);
+}
+
struct oplock_info *opinfo_get(struct ksmbd_file *fp)
{
struct oplock_info *opinfo;
{
struct ksmbd_inode *ci = opinfo->o_fp->f_ci;
- if (opinfo->is_lease) {
- write_lock(&lease_list_lock);
- lease_del_list(opinfo);
- write_unlock(&lease_list_lock);
- }
+ if (opinfo->is_lease)
+ lease_del_open(opinfo);
+
down_write(&ci->m_lock);
list_del(&opinfo->op_entry);
up_write(&ci->m_lock);
}
opinfo->level = SMB2_OPLOCK_LEVEL_II;
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
lease->state = lease->new_state;
+ lease_update_oplock_levels(lease);
+ }
return 0;
}
struct lease *lease = opinfo->o_lease;
lease->state = lease->new_state;
- opinfo->level = SMB2_OPLOCK_LEVEL_II;
+ lease_update_oplock_levels(lease);
return 0;
}
return -EINVAL;
}
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
lease->state = lease->new_state;
+ lease_update_oplock_levels(lease);
+ }
return 0;
}
return -EINVAL;
}
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
lease->state = lease->new_state;
+ lease_update_oplock_levels(lease);
+ }
return 0;
}
lease->new_state = SMB2_LEASE_NONE_LE;
lease->state |= SMB2_LEASE_WRITE_CACHING_LE;
- if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_BATCH;
- else
- opinfo->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ lease_update_oplock_levels(lease);
return 0;
}
lease->new_state = SMB2_LEASE_NONE_LE;
lease->state = new_state;
- if (lease->state & SMB2_LEASE_HANDLE_CACHING_LE)
- if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_BATCH;
- else
- opinfo->level = SMB2_OPLOCK_LEVEL_II;
- else if (lease->state & SMB2_LEASE_WRITE_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
- else if (lease->state & SMB2_LEASE_READ_CACHING_LE)
- opinfo->level = SMB2_OPLOCK_LEVEL_II;
+ lease_update_oplock_levels(lease);
return 0;
}
SMB2_LEASE_HANDLE_CACHING_LE)) {
lease->epoch++;
lease->state = lctx->req_state;
+ lease_update_oplock_levels(lease);
}
}
/* is this a timeout ? */
if (!rc) {
- if (opinfo->is_lease)
+ if (opinfo->is_lease) {
opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
+ lease_update_oplock_levels(opinfo->o_lease);
+ }
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
opinfo->op_state = OPLOCK_STATE_NONE;
}
} else {
__smb2_lease_break_noti(&work->work);
if (opinfo->o_lease->new_state == SMB2_LEASE_NONE_LE) {
- opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
+ lease_update_oplock_levels(opinfo->o_lease);
}
}
return 0;
void destroy_lease_table(struct ksmbd_conn *conn)
{
struct lease_table *lb, *lbtmp;
- struct oplock_info *opinfo;
+ struct lease *lease, *ltmp;
write_lock(&lease_list_lock);
if (list_empty(&lease_table_list)) {
if (conn && memcmp(lb->client_guid, conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE))
continue;
-again:
- rcu_read_lock();
- list_for_each_entry_rcu(opinfo, &lb->lease_list,
- lease_entry) {
- rcu_read_unlock();
- lease_del_list(opinfo);
- goto again;
- }
- rcu_read_unlock();
+ list_for_each_entry_safe(lease, ltmp, &lb->lease_list, l_entry)
+ lease_del_table(lease);
list_del(&lb->l_entry);
kfree(lb);
}
int find_same_lease_key(struct ksmbd_conn *conn, struct ksmbd_inode *ci,
struct lease_ctx_info *lctx)
{
- struct oplock_info *opinfo;
+ struct lease *lease;
int err = 0;
struct lease_table *lb;
return 0;
found:
- rcu_read_lock();
- list_for_each_entry_rcu(opinfo, &lb->lease_list, lease_entry) {
- if (!atomic_inc_not_zero(&opinfo->refcount))
+ list_for_each_entry(lease, &lb->lease_list, l_entry) {
+ if (lease->ci == ci)
continue;
- rcu_read_unlock();
- if (opinfo->o_fp->f_ci == ci)
- goto op_next;
- err = compare_guid_key(opinfo, conn->ClientGUID,
- lctx->lease_key);
- if (err) {
+ if (!memcmp(lease->lease_key, lctx->lease_key,
+ SMB2_LEASE_KEY_SIZE)) {
err = -EINVAL;
ksmbd_debug(OPLOCK,
"found same lease key is already used in other files\n");
- opinfo_put(opinfo);
goto out;
}
-op_next:
- opinfo_put(opinfo);
- rcu_read_lock();
}
- rcu_read_unlock();
out:
read_unlock(&lease_list_lock);
return err;
}
-static void copy_lease(struct oplock_info *op1, struct oplock_info *op2)
-{
- struct lease *lease1 = op1->o_lease;
- struct lease *lease2 = op2->o_lease;
-
- op2->level = op1->level;
- lease2->state = lease1->state;
- memcpy(lease2->lease_key, lease1->lease_key,
- SMB2_LEASE_KEY_SIZE);
- lease2->duration = lease1->duration;
- lease2->flags = lease1->flags;
- lease2->epoch = lease1->epoch;
- lease2->version = lease1->version;
- lease2->is_dir = lease1->is_dir;
- memcpy(lease2->parent_lease_key, lease1->parent_lease_key,
- SMB2_LEASE_KEY_SIZE);
-}
-
-static void add_lease_global_list(struct oplock_info *opinfo,
+static void add_lease_global_list(struct lease *lease, struct ksmbd_conn *conn,
struct lease_table *new_lb)
{
struct lease_table *lb;
write_lock(&lease_list_lock);
list_for_each_entry(lb, &lease_table_list, l_entry) {
- if (!memcmp(lb->client_guid, opinfo->conn->ClientGUID,
+ if (!memcmp(lb->client_guid, conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE)) {
- opinfo->o_lease->l_lb = lb;
- lease_add_list(opinfo);
+ lease_add_table(lease, lb);
write_unlock(&lease_list_lock);
kfree(new_lb);
return;
}
}
- opinfo->o_lease->l_lb = new_lb;
- lease_add_list(opinfo);
+ lease_add_table(lease, new_lb);
list_add(&new_lb->l_entry, &lease_table_list);
write_unlock(&lease_list_lock);
}
struct ksmbd_inode *ci = fp->f_ci;
struct lease_table *new_lb = NULL;
bool prev_op_has_lease;
+ bool new_lease = false;
__le32 prev_op_state = 0;
/* Only v2 leases handle the directory */
return -ENOMEM;
if (lctx) {
- err = alloc_lease(opinfo, lctx);
- if (err)
+ opinfo->o_lease = alloc_lease(lctx, ci);
+ if (!opinfo->o_lease) {
+ err = -ENOMEM;
goto err_out;
+ }
opinfo->is_lease = 1;
+ new_lease = true;
}
/* ci does not have any oplock */
m_opinfo = same_client_has_lease(ci, work->conn->ClientGUID,
lctx);
if (m_opinfo) {
- copy_lease(m_opinfo, opinfo);
+ lease_put(opinfo->o_lease);
+ lease_get(m_opinfo->o_lease);
+ opinfo->o_lease = m_opinfo->o_lease;
+ opinfo->level = m_opinfo->level;
+ new_lease = false;
if (atomic_read(&m_opinfo->breaking_cnt))
opinfo->o_lease->flags |=
SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE;
out:
/*
- * Set o_fp before any publication so that concurrent readers
- * (e.g. find_same_lease_key() on the lease list) that
- * dereference opinfo->o_fp don't hit a NULL pointer.
- *
* Keep the original publication order so concurrent opens can
* still observe the in-flight grant via ci->m_op_list, but make
* everything after opinfo_add() no-fail by preallocating any new
* lease_table first.
*/
opinfo->o_fp = fp;
- if (opinfo->is_lease) {
+ if (new_lease) {
new_lb = alloc_lease_table(opinfo);
if (!new_lb) {
err = -ENOMEM;
opinfo_count_inc(fp);
opinfo_add(opinfo, fp);
+ if (new_lease)
+ add_lease_global_list(opinfo->o_lease, opinfo->conn, new_lb);
if (opinfo->is_lease)
- add_lease_global_list(opinfo, new_lb);
+ lease_add_open(opinfo->o_lease, opinfo);
rcu_assign_pointer(fp->f_opinfo, opinfo);
char *lease_key)
{
struct oplock_info *opinfo = NULL, *ret_op = NULL;
+ struct lease *lease;
struct lease_table *lt;
- int ret;
read_lock(&lease_list_lock);
list_for_each_entry(lt, &lease_table_list, l_entry) {
return NULL;
found:
- rcu_read_lock();
- list_for_each_entry_rcu(opinfo, <->lease_list, lease_entry) {
- if (!atomic_inc_not_zero(&opinfo->refcount))
+ list_for_each_entry(lease, <->lease_list, l_entry) {
+ if (memcmp(lease->lease_key, lease_key, SMB2_LEASE_KEY_SIZE))
continue;
- rcu_read_unlock();
- if (!opinfo->op_state || opinfo->op_state == OPLOCK_CLOSING)
- goto op_next;
- if (!(opinfo->o_lease->state &
- (SMB2_LEASE_HANDLE_CACHING_LE |
- SMB2_LEASE_WRITE_CACHING_LE)))
- goto op_next;
- ret = compare_guid_key(opinfo, conn->ClientGUID,
- lease_key);
- if (ret) {
- ksmbd_debug(OPLOCK, "found opinfo\n");
+ if (!(lease->state & (SMB2_LEASE_HANDLE_CACHING_LE |
+ SMB2_LEASE_WRITE_CACHING_LE)))
+ break;
+
+ spin_lock(&lease->lock);
+ list_for_each_entry(opinfo, &lease->open_list, lease_entry) {
+ if (!opinfo->op_state ||
+ opinfo->op_state == OPLOCK_CLOSING)
+ continue;
+ if (!atomic_inc_not_zero(&opinfo->refcount))
+ continue;
ret_op = opinfo;
+ break;
+ }
+ spin_unlock(&lease->lock);
+ if (ret_op) {
+ ksmbd_debug(OPLOCK, "found opinfo\n");
goto out;
}
-op_next:
- opinfo_put(opinfo);
- rcu_read_lock();
+ break;
}
- rcu_read_unlock();
out:
read_unlock(&lease_list_lock);