return m_opinfo;
}
-static void wait_for_break_ack(struct oplock_info *opinfo)
+static bool wait_for_break_ack(struct oplock_info *opinfo)
{
int rc = 0;
}
opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
opinfo->op_state = OPLOCK_STATE_NONE;
+ return true;
}
+
+ return false;
}
static void wake_up_oplock_break(struct oplock_info *opinfo)
conn = READ_ONCE(opinfo->conn);
if (!conn)
- return 0;
+ return ksmbd_invalidate_durable_fd(opinfo->fid);
work = ksmbd_alloc_work_struct();
if (!work)
INIT_WORK(&work->work, __smb2_oplock_break_noti);
ksmbd_queue_work(work);
- wait_for_break_ack(opinfo);
+ if (wait_for_break_ack(opinfo))
+ ret = ksmbd_invalidate_durable_fd(opinfo->fid);
} else {
__smb2_oplock_break_noti(&work->work);
if (opinfo->level == SMB2_OPLOCK_LEVEL_II)
struct ksmbd_work *work;
struct lease_break_info *br_info;
struct lease *lease = opinfo->o_lease;
+ int ret = 0;
conn = READ_ONCE(opinfo->conn);
if (lease->version == 2 && lease->l_lb && lease->l_lb->conn &&
!ksmbd_conn_releasing(lease->l_lb->conn))
conn = lease->l_lb->conn;
if (!conn)
- return 0;
+ return ksmbd_invalidate_durable_fd(opinfo->fid);
work = ksmbd_alloc_work_struct();
if (!work)
if (opinfo->op_state == OPLOCK_ACK_WAIT) {
INIT_WORK(&work->work, __smb2_lease_break_noti);
ksmbd_queue_work(work);
- if (wait_ack)
- wait_for_break_ack(opinfo);
+ if (wait_ack) {
+ if (wait_for_break_ack(opinfo))
+ ret = ksmbd_invalidate_durable_fd(opinfo->fid);
+ }
} else {
__smb2_lease_break_noti(&work->work);
if (opinfo->o_lease->new_state == SMB2_LEASE_NONE_LE) {
lease_update_oplock_levels(opinfo->o_lease);
}
}
- return 0;
+ return ret;
}
static void wait_lease_breaking(struct oplock_info *opinfo)
struct ksmbd_inode *ci = fp->f_ci;
struct lease_table *new_lb = NULL;
bool prev_op_has_lease;
+ bool prev_durable_open = false;
+ bool prev_durable_detached = false;
+ unsigned long long prev_fid = KSMBD_NO_FID;
bool new_lease = false;
__le32 prev_op_state = 0;
goto op_break_not_needed;
}
+ if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp &&
+ prev_opinfo->o_fp->is_durable) {
+ prev_durable_open = true;
+ prev_durable_detached = !prev_opinfo->o_fp->conn ||
+ !prev_opinfo->o_fp->tcon;
+ prev_fid = prev_opinfo->fid;
+ }
+
err = oplock_break(prev_opinfo, break_level, work);
+ if (prev_durable_detached || (prev_durable_open && err == -ENOENT))
+ ksmbd_invalidate_durable_fd(prev_fid);
opinfo_put(prev_opinfo);
if (err == -ENOENT)
goto set_lev;
if (!opinfo)
return 0;
+ if (ksmbd_has_other_active_fd(fp)) {
+ ksmbd_debug(SMB, "Durable handle reconnect failed: competing open\n");
+ ret = -EBADF;
+ goto out;
+ }
+
if (ksmbd_vfs_compare_durable_owner(fp, user) == false) {
ksmbd_debug(SMB, "Durable handle reconnect failed: owner mismatch\n");
ret = -EBADF;
goto out;
}
+ if (dh_info->fp->durable_volatile_id !=
+ recon_v2->dcontext.Fid.VolatileFileId) {
+ err = -EBADF;
+ ksmbd_put_durable_fd(dh_info->fp);
+ goto out;
+ }
+
if (memcmp(dh_info->fp->create_guid, recon_v2->dcontext.CreateGuid,
SMB2_CREATE_GUID_SIZE)) {
err = -EBADF;
goto out;
}
+ if (dh_info->fp->durable_volatile_id !=
+ recon->Data.Fid.VolatileFileId) {
+ err = -EBADF;
+ ksmbd_put_durable_fd(dh_info->fp);
+ goto out;
+ }
+
dh_info->type = dh_idx;
dh_info->reconnected = true;
ksmbd_debug(SMB, "reconnect Persistent-id from reconnect = %llu\n",
struct ksmbd_file *fp;
fp = __ksmbd_lookup_fd(&global_ft, id);
- if (fp && (fp->conn ||
+ if (fp && (fp->durable_reconnect_disabled ||
+ fp->conn ||
(fp->durable_scavenger_timeout &&
(fp->durable_scavenger_timeout <
jiffies_to_msecs(jiffies))))) {
__ksmbd_close_fd(NULL, fp);
}
+bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
+{
+ struct ksmbd_file *lfp;
+ struct ksmbd_inode *ci = fp->f_ci;
+ bool ret = false;
+
+ down_read(&ci->m_lock);
+ list_for_each_entry(lfp, &ci->m_fp_list, node) {
+ if (lfp == fp)
+ continue;
+
+ if (lfp->f_state == FP_INITED &&
+ (READ_ONCE(lfp->conn) || READ_ONCE(lfp->tcon))) {
+ ret = true;
+ break;
+ }
+ }
+ up_read(&ci->m_lock);
+
+ return ret;
+}
+
+int ksmbd_invalidate_durable_fd(unsigned long long id)
+{
+ struct ksmbd_file *fp;
+
+ fp = ksmbd_lookup_global_fd(id);
+ if (!fp)
+ return -ENOENT;
+
+ fp->durable_reconnect_disabled = true;
+
+ if (fp->conn) {
+ ksmbd_put_durable_fd(fp);
+ return -ENOENT;
+ }
+
+ fp->durable_timeout = 1;
+ fp->durable_scavenger_timeout = jiffies_to_msecs(jiffies);
+ ksmbd_put_durable_fd(fp);
+ if (waitqueue_active(&dh_wq))
+ wake_up(&dh_wq);
+
+ return -ENOENT;
+}
+
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid)
{
struct ksmbd_file *fp = NULL;
* global_ft.
*/
idr_remove(ft->idr, id);
+ fp->durable_volatile_id = fp->volatile_id;
fp->volatile_id = KSMBD_NO_FID;
write_unlock(&ft->lock);
struct file *filp;
u64 persistent_id;
u64 volatile_id;
+ u64 durable_volatile_id;
spinlock_t f_lock;
bool is_durable;
bool is_persistent;
bool is_resilient;
+ bool durable_reconnect_disabled;
bool is_posix_ctxt;
struct durable_owner owner;
struct ksmbd_file *ksmbd_lookup_global_fd(unsigned long long id);
struct ksmbd_file *ksmbd_lookup_durable_fd(unsigned long long id);
void ksmbd_put_durable_fd(struct ksmbd_file *fp);
+int ksmbd_invalidate_durable_fd(unsigned long long id);
+bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
unsigned int ksmbd_open_durable_fd(struct ksmbd_file *fp);