|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * Functions to handle the cached directory entries |
| 4 | + * |
| 5 | + * Copyright (c) 2022, Ronnie Sahlberg <[email protected]> |
| 6 | + */ |
| 7 | + |
| 8 | +#include "cifsglob.h" |
| 9 | +#include "cifsproto.h" |
| 10 | +#include "cifs_debug.h" |
| 11 | +#include "smb2proto.h" |
| 12 | +#include "cached_dir.h" |
| 13 | + |
| 14 | +/* |
| 15 | + * Open the and cache a directory handle. |
| 16 | + * If error then *cfid is not initialized. |
| 17 | + */ |
| 18 | +int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, |
| 19 | + const char *path, |
| 20 | + struct cifs_sb_info *cifs_sb, |
| 21 | + bool lookup_only, struct cached_fid **ret_cfid) |
| 22 | +{ |
| 23 | + struct cifs_ses *ses; |
| 24 | + struct TCP_Server_Info *server; |
| 25 | + struct cifs_open_parms oparms; |
| 26 | + struct smb2_create_rsp *o_rsp = NULL; |
| 27 | + struct smb2_query_info_rsp *qi_rsp = NULL; |
| 28 | + int resp_buftype[2]; |
| 29 | + struct smb_rqst rqst[2]; |
| 30 | + struct kvec rsp_iov[2]; |
| 31 | + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; |
| 32 | + struct kvec qi_iov[1]; |
| 33 | + int rc, flags = 0; |
| 34 | + __le16 utf16_path = 0; /* Null - since an open of top of share */ |
| 35 | + u8 oplock = SMB2_OPLOCK_LEVEL_II; |
| 36 | + struct cifs_fid *pfid; |
| 37 | + struct dentry *dentry; |
| 38 | + struct cached_fid *cfid; |
| 39 | + |
| 40 | + if (tcon == NULL || tcon->nohandlecache || |
| 41 | + is_smb1_server(tcon->ses->server)) |
| 42 | + return -EOPNOTSUPP; |
| 43 | + |
| 44 | + ses = tcon->ses; |
| 45 | + server = ses->server; |
| 46 | + |
| 47 | + if (cifs_sb->root == NULL) |
| 48 | + return -ENOENT; |
| 49 | + |
| 50 | + if (strlen(path)) |
| 51 | + return -ENOENT; |
| 52 | + |
| 53 | + dentry = cifs_sb->root; |
| 54 | + |
| 55 | + cfid = tcon->cfid; |
| 56 | + mutex_lock(&cfid->fid_mutex); |
| 57 | + if (cfid->is_valid) { |
| 58 | + cifs_dbg(FYI, "found a cached root file handle\n"); |
| 59 | + *ret_cfid = cfid; |
| 60 | + kref_get(&cfid->refcount); |
| 61 | + mutex_unlock(&cfid->fid_mutex); |
| 62 | + return 0; |
| 63 | + } |
| 64 | + |
| 65 | + /* |
| 66 | + * We do not hold the lock for the open because in case |
| 67 | + * SMB2_open needs to reconnect, it will end up calling |
| 68 | + * cifs_mark_open_files_invalid() which takes the lock again |
| 69 | + * thus causing a deadlock |
| 70 | + */ |
| 71 | + mutex_unlock(&cfid->fid_mutex); |
| 72 | + |
| 73 | + if (lookup_only) |
| 74 | + return -ENOENT; |
| 75 | + |
| 76 | + if (smb3_encryption_required(tcon)) |
| 77 | + flags |= CIFS_TRANSFORM_REQ; |
| 78 | + |
| 79 | + if (!server->ops->new_lease_key) |
| 80 | + return -EIO; |
| 81 | + |
| 82 | + pfid = &cfid->fid; |
| 83 | + server->ops->new_lease_key(pfid); |
| 84 | + |
| 85 | + memset(rqst, 0, sizeof(rqst)); |
| 86 | + resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; |
| 87 | + memset(rsp_iov, 0, sizeof(rsp_iov)); |
| 88 | + |
| 89 | + /* Open */ |
| 90 | + memset(&open_iov, 0, sizeof(open_iov)); |
| 91 | + rqst[0].rq_iov = open_iov; |
| 92 | + rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; |
| 93 | + |
| 94 | + oparms.tcon = tcon; |
| 95 | + oparms.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE); |
| 96 | + oparms.desired_access = FILE_READ_ATTRIBUTES; |
| 97 | + oparms.disposition = FILE_OPEN; |
| 98 | + oparms.fid = pfid; |
| 99 | + oparms.reconnect = false; |
| 100 | + |
| 101 | + rc = SMB2_open_init(tcon, server, |
| 102 | + &rqst[0], &oplock, &oparms, &utf16_path); |
| 103 | + if (rc) |
| 104 | + goto oshr_free; |
| 105 | + smb2_set_next_command(tcon, &rqst[0]); |
| 106 | + |
| 107 | + memset(&qi_iov, 0, sizeof(qi_iov)); |
| 108 | + rqst[1].rq_iov = qi_iov; |
| 109 | + rqst[1].rq_nvec = 1; |
| 110 | + |
| 111 | + rc = SMB2_query_info_init(tcon, server, |
| 112 | + &rqst[1], COMPOUND_FID, |
| 113 | + COMPOUND_FID, FILE_ALL_INFORMATION, |
| 114 | + SMB2_O_INFO_FILE, 0, |
| 115 | + sizeof(struct smb2_file_all_info) + |
| 116 | + PATH_MAX * 2, 0, NULL); |
| 117 | + if (rc) |
| 118 | + goto oshr_free; |
| 119 | + |
| 120 | + smb2_set_related(&rqst[1]); |
| 121 | + |
| 122 | + rc = compound_send_recv(xid, ses, server, |
| 123 | + flags, 2, rqst, |
| 124 | + resp_buftype, rsp_iov); |
| 125 | + mutex_lock(&cfid->fid_mutex); |
| 126 | + |
| 127 | + /* |
| 128 | + * Now we need to check again as the cached root might have |
| 129 | + * been successfully re-opened from a concurrent process |
| 130 | + */ |
| 131 | + |
| 132 | + if (cfid->is_valid) { |
| 133 | + /* work was already done */ |
| 134 | + |
| 135 | + /* stash fids for close() later */ |
| 136 | + struct cifs_fid fid = { |
| 137 | + .persistent_fid = pfid->persistent_fid, |
| 138 | + .volatile_fid = pfid->volatile_fid, |
| 139 | + }; |
| 140 | + |
| 141 | + /* |
| 142 | + * caller expects this func to set the fid in cfid to valid |
| 143 | + * cached root, so increment the refcount. |
| 144 | + */ |
| 145 | + kref_get(&cfid->refcount); |
| 146 | + |
| 147 | + mutex_unlock(&cfid->fid_mutex); |
| 148 | + |
| 149 | + if (rc == 0) { |
| 150 | + /* close extra handle outside of crit sec */ |
| 151 | + SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); |
| 152 | + } |
| 153 | + rc = 0; |
| 154 | + goto oshr_free; |
| 155 | + } |
| 156 | + |
| 157 | + /* Cached root is still invalid, continue normaly */ |
| 158 | + |
| 159 | + if (rc) { |
| 160 | + if (rc == -EREMCHG) { |
| 161 | + tcon->need_reconnect = true; |
| 162 | + pr_warn_once("server share %s deleted\n", |
| 163 | + tcon->treeName); |
| 164 | + } |
| 165 | + goto oshr_exit; |
| 166 | + } |
| 167 | + |
| 168 | + atomic_inc(&tcon->num_remote_opens); |
| 169 | + |
| 170 | + o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; |
| 171 | + oparms.fid->persistent_fid = o_rsp->PersistentFileId; |
| 172 | + oparms.fid->volatile_fid = o_rsp->VolatileFileId; |
| 173 | +#ifdef CONFIG_CIFS_DEBUG2 |
| 174 | + oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); |
| 175 | +#endif /* CIFS_DEBUG2 */ |
| 176 | + |
| 177 | + cfid->tcon = tcon; |
| 178 | + cfid->is_valid = true; |
| 179 | + cfid->dentry = dentry; |
| 180 | + dget(dentry); |
| 181 | + kref_init(&cfid->refcount); |
| 182 | + |
| 183 | + /* BB TBD check to see if oplock level check can be removed below */ |
| 184 | + if (o_rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE) { |
| 185 | + /* |
| 186 | + * See commit 2f94a3125b87. Increment the refcount when we |
| 187 | + * get a lease for root, release it if lease break occurs |
| 188 | + */ |
| 189 | + kref_get(&cfid->refcount); |
| 190 | + cfid->has_lease = true; |
| 191 | + smb2_parse_contexts(server, o_rsp, |
| 192 | + &oparms.fid->epoch, |
| 193 | + oparms.fid->lease_key, &oplock, |
| 194 | + NULL, NULL); |
| 195 | + } else |
| 196 | + goto oshr_exit; |
| 197 | + |
| 198 | + qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base; |
| 199 | + if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info)) |
| 200 | + goto oshr_exit; |
| 201 | + if (!smb2_validate_and_copy_iov( |
| 202 | + le16_to_cpu(qi_rsp->OutputBufferOffset), |
| 203 | + sizeof(struct smb2_file_all_info), |
| 204 | + &rsp_iov[1], sizeof(struct smb2_file_all_info), |
| 205 | + (char *)&cfid->file_all_info)) |
| 206 | + cfid->file_all_info_is_valid = true; |
| 207 | + |
| 208 | + cfid->time = jiffies; |
| 209 | + |
| 210 | +oshr_exit: |
| 211 | + mutex_unlock(&cfid->fid_mutex); |
| 212 | +oshr_free: |
| 213 | + SMB2_open_free(&rqst[0]); |
| 214 | + SMB2_query_info_free(&rqst[1]); |
| 215 | + free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); |
| 216 | + free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); |
| 217 | + if (rc == 0) |
| 218 | + *ret_cfid = cfid; |
| 219 | + |
| 220 | + return rc; |
| 221 | +} |
| 222 | + |
| 223 | +int open_cached_dir_by_dentry(struct cifs_tcon *tcon, |
| 224 | + struct dentry *dentry, |
| 225 | + struct cached_fid **ret_cfid) |
| 226 | +{ |
| 227 | + struct cached_fid *cfid; |
| 228 | + |
| 229 | + cfid = tcon->cfid; |
| 230 | + |
| 231 | + mutex_lock(&cfid->fid_mutex); |
| 232 | + if (cfid->dentry == dentry) { |
| 233 | + cifs_dbg(FYI, "found a cached root file handle by dentry\n"); |
| 234 | + *ret_cfid = cfid; |
| 235 | + kref_get(&cfid->refcount); |
| 236 | + mutex_unlock(&cfid->fid_mutex); |
| 237 | + return 0; |
| 238 | + } |
| 239 | + mutex_unlock(&cfid->fid_mutex); |
| 240 | + return -ENOENT; |
| 241 | +} |
| 242 | + |
| 243 | +static void |
| 244 | +smb2_close_cached_fid(struct kref *ref) |
| 245 | +{ |
| 246 | + struct cached_fid *cfid = container_of(ref, struct cached_fid, |
| 247 | + refcount); |
| 248 | + struct cached_dirent *dirent, *q; |
| 249 | + |
| 250 | + if (cfid->is_valid) { |
| 251 | + cifs_dbg(FYI, "clear cached root file handle\n"); |
| 252 | + SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid, |
| 253 | + cfid->fid.volatile_fid); |
| 254 | + } |
| 255 | + |
| 256 | + /* |
| 257 | + * We only check validity above to send SMB2_close, |
| 258 | + * but we still need to invalidate these entries |
| 259 | + * when this function is called |
| 260 | + */ |
| 261 | + cfid->is_valid = false; |
| 262 | + cfid->file_all_info_is_valid = false; |
| 263 | + cfid->has_lease = false; |
| 264 | + if (cfid->dentry) { |
| 265 | + dput(cfid->dentry); |
| 266 | + cfid->dentry = NULL; |
| 267 | + } |
| 268 | + /* |
| 269 | + * Delete all cached dirent names |
| 270 | + */ |
| 271 | + mutex_lock(&cfid->dirents.de_mutex); |
| 272 | + list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) { |
| 273 | + list_del(&dirent->entry); |
| 274 | + kfree(dirent->name); |
| 275 | + kfree(dirent); |
| 276 | + } |
| 277 | + cfid->dirents.is_valid = 0; |
| 278 | + cfid->dirents.is_failed = 0; |
| 279 | + cfid->dirents.ctx = NULL; |
| 280 | + cfid->dirents.pos = 0; |
| 281 | + mutex_unlock(&cfid->dirents.de_mutex); |
| 282 | + |
| 283 | +} |
| 284 | + |
| 285 | +void close_cached_dir(struct cached_fid *cfid) |
| 286 | +{ |
| 287 | + mutex_lock(&cfid->fid_mutex); |
| 288 | + kref_put(&cfid->refcount, smb2_close_cached_fid); |
| 289 | + mutex_unlock(&cfid->fid_mutex); |
| 290 | +} |
| 291 | + |
| 292 | +void close_cached_dir_lease_locked(struct cached_fid *cfid) |
| 293 | +{ |
| 294 | + if (cfid->has_lease) { |
| 295 | + cfid->has_lease = false; |
| 296 | + kref_put(&cfid->refcount, smb2_close_cached_fid); |
| 297 | + } |
| 298 | +} |
| 299 | + |
| 300 | +void close_cached_dir_lease(struct cached_fid *cfid) |
| 301 | +{ |
| 302 | + mutex_lock(&cfid->fid_mutex); |
| 303 | + close_cached_dir_lease_locked(cfid); |
| 304 | + mutex_unlock(&cfid->fid_mutex); |
| 305 | +} |
| 306 | + |
| 307 | +/* |
| 308 | + * Called from cifs_kill_sb when we unmount a share |
| 309 | + */ |
| 310 | +void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) |
| 311 | +{ |
| 312 | + struct rb_root *root = &cifs_sb->tlink_tree; |
| 313 | + struct rb_node *node; |
| 314 | + struct cached_fid *cfid; |
| 315 | + struct cifs_tcon *tcon; |
| 316 | + struct tcon_link *tlink; |
| 317 | + |
| 318 | + for (node = rb_first(root); node; node = rb_next(node)) { |
| 319 | + tlink = rb_entry(node, struct tcon_link, tl_rbnode); |
| 320 | + tcon = tlink_tcon(tlink); |
| 321 | + if (IS_ERR(tcon)) |
| 322 | + continue; |
| 323 | + cfid = tcon->cfid; |
| 324 | + mutex_lock(&cfid->fid_mutex); |
| 325 | + if (cfid->dentry) { |
| 326 | + dput(cfid->dentry); |
| 327 | + cfid->dentry = NULL; |
| 328 | + } |
| 329 | + mutex_unlock(&cfid->fid_mutex); |
| 330 | + } |
| 331 | +} |
| 332 | + |
| 333 | +/* |
| 334 | + * Invalidate and close all cached dirs when a TCON has been reset |
| 335 | + * due to a session loss. |
| 336 | + */ |
| 337 | +void invalidate_all_cached_dirs(struct cifs_tcon *tcon) |
| 338 | +{ |
| 339 | + mutex_lock(&tcon->cfid->fid_mutex); |
| 340 | + tcon->cfid->is_valid = false; |
| 341 | + /* cached handle is not valid, so SMB2_CLOSE won't be sent below */ |
| 342 | + close_cached_dir_lease_locked(tcon->cfid); |
| 343 | + memset(&tcon->cfid->fid, 0, sizeof(struct cifs_fid)); |
| 344 | + mutex_unlock(&tcon->cfid->fid_mutex); |
| 345 | +} |
| 346 | + |
| 347 | +static void |
| 348 | +smb2_cached_lease_break(struct work_struct *work) |
| 349 | +{ |
| 350 | + struct cached_fid *cfid = container_of(work, |
| 351 | + struct cached_fid, lease_break); |
| 352 | + |
| 353 | + close_cached_dir_lease(cfid); |
| 354 | +} |
| 355 | + |
| 356 | +int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) |
| 357 | +{ |
| 358 | + if (tcon->cfid->is_valid && |
| 359 | + !memcmp(lease_key, |
| 360 | + tcon->cfid->fid.lease_key, |
| 361 | + SMB2_LEASE_KEY_SIZE)) { |
| 362 | + tcon->cfid->time = 0; |
| 363 | + INIT_WORK(&tcon->cfid->lease_break, |
| 364 | + smb2_cached_lease_break); |
| 365 | + queue_work(cifsiod_wq, |
| 366 | + &tcon->cfid->lease_break); |
| 367 | + return true; |
| 368 | + } |
| 369 | + return false; |
| 370 | +} |
| 371 | + |
| 372 | +struct cached_fid *init_cached_dir(void) |
| 373 | +{ |
| 374 | + struct cached_fid *cfid; |
| 375 | + |
| 376 | + cfid = kzalloc(sizeof(*cfid), GFP_KERNEL); |
| 377 | + if (!cfid) |
| 378 | + return NULL; |
| 379 | + INIT_LIST_HEAD(&cfid->dirents.entries); |
| 380 | + mutex_init(&cfid->dirents.de_mutex); |
| 381 | + mutex_init(&cfid->fid_mutex); |
| 382 | + return cfid; |
| 383 | +} |
| 384 | + |
| 385 | +void free_cached_dir(struct cifs_tcon *tcon) |
| 386 | +{ |
| 387 | + kfree(tcon->cfid); |
| 388 | +} |
0 commit comments