1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2018 Samsung Electronics Co., Ltd. 4 */ 5 6 #include <linux/list.h> 7 #include <linux/slab.h> 8 #include <linux/xarray.h> 9 10 #include "../transport_ipc.h" 11 #include "../connection.h" 12 13 #include "tree_connect.h" 14 #include "user_config.h" 15 #include "share_config.h" 16 #include "user_session.h" 17 18 struct ksmbd_tree_conn_status 19 ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name) 20 { 21 struct ksmbd_tree_conn_status status = {-ENOENT, NULL}; 22 struct ksmbd_tree_connect_response *resp = NULL; 23 struct ksmbd_share_config *sc; 24 struct ksmbd_tree_connect *tree_conn = NULL; 25 struct sockaddr *peer_addr; 26 struct ksmbd_conn *conn = work->conn; 27 struct ksmbd_session *sess = work->sess; 28 int ret; 29 30 sc = ksmbd_share_config_get(work, share_name); 31 if (!sc) 32 return status; 33 34 tree_conn = kzalloc(sizeof(struct ksmbd_tree_connect), GFP_KERNEL); 35 if (!tree_conn) { 36 status.ret = -ENOMEM; 37 goto out_error; 38 } 39 40 tree_conn->id = ksmbd_acquire_tree_conn_id(sess); 41 if (tree_conn->id < 0) { 42 status.ret = -EINVAL; 43 goto out_error; 44 } 45 46 peer_addr = KSMBD_TCP_PEER_SOCKADDR(conn); 47 resp = ksmbd_ipc_tree_connect_request(sess, 48 sc, 49 tree_conn, 50 peer_addr); 51 if (!resp) { 52 status.ret = -EINVAL; 53 goto out_error; 54 } 55 56 status.ret = resp->status; 57 if (status.ret != KSMBD_TREE_CONN_STATUS_OK) 58 goto out_error; 59 60 tree_conn->flags = resp->connection_flags; 61 if (test_tree_conn_flag(tree_conn, KSMBD_TREE_CONN_FLAG_UPDATE)) { 62 struct ksmbd_share_config *new_sc; 63 64 ksmbd_share_config_del(sc); 65 new_sc = ksmbd_share_config_get(work, share_name); 66 if (!new_sc) { 67 pr_err("Failed to update stale share config\n"); 68 status.ret = -ESTALE; 69 goto out_error; 70 } 71 ksmbd_share_config_put(sc); 72 sc = new_sc; 73 } 74 75 tree_conn->user = sess->user; 76 tree_conn->share_conf = sc; 77 tree_conn->t_state = TREE_NEW; 78 status.tree_conn = tree_conn; 79 atomic_set(&tree_conn->refcount, 1); 80 init_waitqueue_head(&tree_conn->refcount_q); 81 82 ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, 83 GFP_KERNEL)); 84 if (ret) { 85 status.ret = -ENOMEM; 86 goto out_error; 87 } 88 kvfree(resp); 89 return status; 90 91 out_error: 92 if (tree_conn) 93 ksmbd_release_tree_conn_id(sess, tree_conn->id); 94 ksmbd_share_config_put(sc); 95 kfree(tree_conn); 96 kvfree(resp); 97 return status; 98 } 99 100 void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon) 101 { 102 /* 103 * Checking waitqueue to releasing tree connect on 104 * tree disconnect. waitqueue_active is safe because it 105 * uses atomic operation for condition. 106 */ 107 if (!atomic_dec_return(&tcon->refcount) && 108 waitqueue_active(&tcon->refcount_q)) 109 wake_up(&tcon->refcount_q); 110 } 111 112 int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, 113 struct ksmbd_tree_connect *tree_conn) 114 { 115 int ret; 116 117 write_lock(&sess->tree_conns_lock); 118 xa_erase(&sess->tree_conns, tree_conn->id); 119 write_unlock(&sess->tree_conns_lock); 120 121 if (!atomic_dec_and_test(&tree_conn->refcount)) 122 wait_event(tree_conn->refcount_q, 123 atomic_read(&tree_conn->refcount) == 0); 124 125 ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id); 126 ksmbd_release_tree_conn_id(sess, tree_conn->id); 127 ksmbd_share_config_put(tree_conn->share_conf); 128 kfree(tree_conn); 129 return ret; 130 } 131 132 struct ksmbd_tree_connect *ksmbd_tree_conn_lookup(struct ksmbd_session *sess, 133 unsigned int id) 134 { 135 struct ksmbd_tree_connect *tcon; 136 137 read_lock(&sess->tree_conns_lock); 138 tcon = xa_load(&sess->tree_conns, id); 139 if (tcon) { 140 if (tcon->t_state != TREE_CONNECTED) 141 tcon = NULL; 142 else if (!atomic_inc_not_zero(&tcon->refcount)) 143 tcon = NULL; 144 } 145 read_unlock(&sess->tree_conns_lock); 146 147 return tcon; 148 } 149 150 int ksmbd_tree_conn_session_logoff(struct ksmbd_session *sess) 151 { 152 int ret = 0; 153 struct ksmbd_tree_connect *tc; 154 unsigned long id; 155 156 if (!sess) 157 return -EINVAL; 158 159 xa_for_each(&sess->tree_conns, id, tc) { 160 write_lock(&sess->tree_conns_lock); 161 if (tc->t_state == TREE_DISCONNECTED) { 162 write_unlock(&sess->tree_conns_lock); 163 ret = -ENOENT; 164 continue; 165 } 166 tc->t_state = TREE_DISCONNECTED; 167 write_unlock(&sess->tree_conns_lock); 168 169 ret |= ksmbd_tree_conn_disconnect(sess, tc); 170 } 171 xa_destroy(&sess->tree_conns); 172 return ret; 173 } 174