// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2018 Samsung Electronics Co., Ltd. */ #include #include #include #include "../transport_ipc.h" #include "../connection.h" #include "tree_connect.h" #include "user_config.h" #include "share_config.h" #include "user_session.h" struct ksmbd_tree_conn_status ksmbd_tree_conn_connect(struct ksmbd_conn *conn, struct ksmbd_session *sess, const char *share_name) { struct ksmbd_tree_conn_status status = {-ENOENT, NULL}; struct ksmbd_tree_connect_response *resp = NULL; struct ksmbd_share_config *sc; struct ksmbd_tree_connect *tree_conn = NULL; struct sockaddr *peer_addr; int ret; sc = ksmbd_share_config_get(conn->um, share_name); if (!sc) return status; tree_conn = kzalloc(sizeof(struct ksmbd_tree_connect), GFP_KERNEL); if (!tree_conn) { status.ret = -ENOMEM; goto out_error; } tree_conn->id = ksmbd_acquire_tree_conn_id(sess); if (tree_conn->id < 0) { status.ret = -EINVAL; goto out_error; } peer_addr = KSMBD_TCP_PEER_SOCKADDR(conn); resp = ksmbd_ipc_tree_connect_request(sess, sc, tree_conn, peer_addr); if (!resp) { status.ret = -EINVAL; goto out_error; } status.ret = resp->status; if (status.ret != KSMBD_TREE_CONN_STATUS_OK) goto out_error; tree_conn->flags = resp->connection_flags; if (test_tree_conn_flag(tree_conn, KSMBD_TREE_CONN_FLAG_UPDATE)) { struct ksmbd_share_config *new_sc; ksmbd_share_config_del(sc); new_sc = ksmbd_share_config_get(conn->um, share_name); if (!new_sc) { pr_err("Failed to update stale share config\n"); status.ret = -ESTALE; goto out_error; } ksmbd_share_config_put(sc); sc = new_sc; } tree_conn->user = sess->user; tree_conn->share_conf = sc; tree_conn->t_state = TREE_NEW; status.tree_conn = tree_conn; atomic_set(&tree_conn->refcount, 1); init_waitqueue_head(&tree_conn->refcount_q); ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, GFP_KERNEL)); if (ret) { status.ret = -ENOMEM; goto out_error; } kvfree(resp); return status; out_error: if (tree_conn) ksmbd_release_tree_conn_id(sess, tree_conn->id); ksmbd_share_config_put(sc); kfree(tree_conn); kvfree(resp); return status; } void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon) { /* * Checking waitqueue to releasing tree connect on * tree disconnect. waitqueue_active is safe because it * uses atomic operation for condition. */ if (!atomic_dec_return(&tcon->refcount) && waitqueue_active(&tcon->refcount_q)) wake_up(&tcon->refcount_q); } int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, struct ksmbd_tree_connect *tree_conn) { int ret; write_lock(&sess->tree_conns_lock); xa_erase(&sess->tree_conns, tree_conn->id); write_unlock(&sess->tree_conns_lock); if (!atomic_dec_and_test(&tree_conn->refcount)) wait_event(tree_conn->refcount_q, atomic_read(&tree_conn->refcount) == 0); ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id); ksmbd_release_tree_conn_id(sess, tree_conn->id); ksmbd_share_config_put(tree_conn->share_conf); kfree(tree_conn); return ret; } struct ksmbd_tree_connect *ksmbd_tree_conn_lookup(struct ksmbd_session *sess, unsigned int id) { struct ksmbd_tree_connect *tcon; read_lock(&sess->tree_conns_lock); tcon = xa_load(&sess->tree_conns, id); if (tcon) { if (tcon->t_state != TREE_CONNECTED) tcon = NULL; else if (!atomic_inc_not_zero(&tcon->refcount)) tcon = NULL; } read_unlock(&sess->tree_conns_lock); return tcon; } int ksmbd_tree_conn_session_logoff(struct ksmbd_session *sess) { int ret = 0; struct ksmbd_tree_connect *tc; unsigned long id; if (!sess) return -EINVAL; xa_for_each(&sess->tree_conns, id, tc) { write_lock(&sess->tree_conns_lock); if (tc->t_state == TREE_DISCONNECTED) { write_unlock(&sess->tree_conns_lock); ret = -ENOENT; continue; } tc->t_state = TREE_DISCONNECTED; write_unlock(&sess->tree_conns_lock); ret |= ksmbd_tree_conn_disconnect(sess, tc); } xa_destroy(&sess->tree_conns); return ret; }