brintos

brintos / linux-shallow public Read only

0
0
Text · 17.4 KiB · 0ff2491 Raw
717 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 *  Functions to handle the cached directory entries4 *5 *  Copyright (c) 2022, Ronnie Sahlberg <lsahlber@redhat.com>6 */7 8#include <linux/namei.h>9#include "cifsglob.h"10#include "cifsproto.h"11#include "cifs_debug.h"12#include "smb2proto.h"13#include "cached_dir.h"14 15static struct cached_fid *init_cached_dir(const char *path);16static void free_cached_dir(struct cached_fid *cfid);17static void smb2_close_cached_fid(struct kref *ref);18static void cfids_laundromat_worker(struct work_struct *work);19 20static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,21						    const char *path,22						    bool lookup_only,23						    __u32 max_cached_dirs)24{25	struct cached_fid *cfid;26 27	spin_lock(&cfids->cfid_list_lock);28	list_for_each_entry(cfid, &cfids->entries, entry) {29		if (!strcmp(cfid->path, path)) {30			/*31			 * If it doesn't have a lease it is either not yet32			 * fully cached or it may be in the process of33			 * being deleted due to a lease break.34			 */35			if (!cfid->time || !cfid->has_lease) {36				spin_unlock(&cfids->cfid_list_lock);37				return NULL;38			}39			kref_get(&cfid->refcount);40			spin_unlock(&cfids->cfid_list_lock);41			return cfid;42		}43	}44	if (lookup_only) {45		spin_unlock(&cfids->cfid_list_lock);46		return NULL;47	}48	if (cfids->num_entries >= max_cached_dirs) {49		spin_unlock(&cfids->cfid_list_lock);50		return NULL;51	}52	cfid = init_cached_dir(path);53	if (cfid == NULL) {54		spin_unlock(&cfids->cfid_list_lock);55		return NULL;56	}57	cfid->cfids = cfids;58	cfids->num_entries++;59	list_add(&cfid->entry, &cfids->entries);60	cfid->on_list = true;61	kref_get(&cfid->refcount);62	spin_unlock(&cfids->cfid_list_lock);63	return cfid;64}65 66static struct dentry *67path_to_dentry(struct cifs_sb_info *cifs_sb, const char *path)68{69	struct dentry *dentry;70	const char *s, *p;71	char sep;72 73	sep = CIFS_DIR_SEP(cifs_sb);74	dentry = dget(cifs_sb->root);75	s = path;76 77	do {78		struct inode *dir = d_inode(dentry);79		struct dentry *child;80 81		if (!S_ISDIR(dir->i_mode)) {82			dput(dentry);83			dentry = ERR_PTR(-ENOTDIR);84			break;85		}86 87		/* skip separators */88		while (*s == sep)89			s++;90		if (!*s)91			break;92		p = s++;93		/* next separator */94		while (*s && *s != sep)95			s++;96 97		child = lookup_positive_unlocked(p, dentry, s - p);98		dput(dentry);99		dentry = child;100	} while (!IS_ERR(dentry));101	return dentry;102}103 104static const char *path_no_prefix(struct cifs_sb_info *cifs_sb,105				  const char *path)106{107	size_t len = 0;108 109	if (!*path)110		return path;111 112	if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&113	    cifs_sb->prepath) {114		len = strlen(cifs_sb->prepath) + 1;115		if (unlikely(len > strlen(path)))116			return ERR_PTR(-EINVAL);117	}118	return path + len;119}120 121/*122 * Open the and cache a directory handle.123 * If error then *cfid is not initialized.124 */125int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,126		    const char *path,127		    struct cifs_sb_info *cifs_sb,128		    bool lookup_only, struct cached_fid **ret_cfid)129{130	struct cifs_ses *ses;131	struct TCP_Server_Info *server;132	struct cifs_open_parms oparms;133	struct smb2_create_rsp *o_rsp = NULL;134	struct smb2_query_info_rsp *qi_rsp = NULL;135	int resp_buftype[2];136	struct smb_rqst rqst[2];137	struct kvec rsp_iov[2];138	struct kvec open_iov[SMB2_CREATE_IOV_SIZE];139	struct kvec qi_iov[1];140	int rc, flags = 0;141	__le16 *utf16_path = NULL;142	u8 oplock = SMB2_OPLOCK_LEVEL_II;143	struct cifs_fid *pfid;144	struct dentry *dentry = NULL;145	struct cached_fid *cfid;146	struct cached_fids *cfids;147	const char *npath;148	int retries = 0, cur_sleep = 1;149 150	if (tcon == NULL || tcon->cfids == NULL || tcon->nohandlecache ||151	    is_smb1_server(tcon->ses->server) || (dir_cache_timeout == 0))152		return -EOPNOTSUPP;153 154	ses = tcon->ses;155	cfids = tcon->cfids;156 157	if (cifs_sb->root == NULL)158		return -ENOENT;159 160replay_again:161	/* reinitialize for possible replay */162	flags = 0;163	oplock = SMB2_OPLOCK_LEVEL_II;164	server = cifs_pick_channel(ses);165 166	if (!server->ops->new_lease_key)167		return -EIO;168 169	utf16_path = cifs_convert_path_to_utf16(path, cifs_sb);170	if (!utf16_path)171		return -ENOMEM;172 173	cfid = find_or_create_cached_dir(cfids, path, lookup_only, tcon->max_cached_dirs);174	if (cfid == NULL) {175		kfree(utf16_path);176		return -ENOENT;177	}178	/*179	 * Return cached fid if it has a lease.  Otherwise, it is either a new180	 * entry or laundromat worker removed it from @cfids->entries.  Caller181	 * will put last reference if the latter.182	 */183	spin_lock(&cfids->cfid_list_lock);184	if (cfid->has_lease) {185		spin_unlock(&cfids->cfid_list_lock);186		*ret_cfid = cfid;187		kfree(utf16_path);188		return 0;189	}190	spin_unlock(&cfids->cfid_list_lock);191 192	/*193	 * Skip any prefix paths in @path as lookup_positive_unlocked() ends up194	 * calling ->lookup() which already adds those through195	 * build_path_from_dentry().  Also, do it earlier as we might reconnect196	 * below when trying to send compounded request and then potentially197	 * having a different prefix path (e.g. after DFS failover).198	 */199	npath = path_no_prefix(cifs_sb, path);200	if (IS_ERR(npath)) {201		rc = PTR_ERR(npath);202		goto out;203	}204 205	if (!npath[0]) {206		dentry = dget(cifs_sb->root);207	} else {208		dentry = path_to_dentry(cifs_sb, npath);209		if (IS_ERR(dentry)) {210			rc = -ENOENT;211			goto out;212		}213	}214	cfid->dentry = dentry;215 216	/*217	 * We do not hold the lock for the open because in case218	 * SMB2_open needs to reconnect.219	 * This is safe because no other thread will be able to get a ref220	 * to the cfid until we have finished opening the file and (possibly)221	 * acquired a lease.222	 */223	if (smb3_encryption_required(tcon))224		flags |= CIFS_TRANSFORM_REQ;225 226	pfid = &cfid->fid;227	server->ops->new_lease_key(pfid);228 229	memset(rqst, 0, sizeof(rqst));230	resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER;231	memset(rsp_iov, 0, sizeof(rsp_iov));232 233	/* Open */234	memset(&open_iov, 0, sizeof(open_iov));235	rqst[0].rq_iov = open_iov;236	rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE;237 238	oparms = (struct cifs_open_parms) {239		.tcon = tcon,240		.path = path,241		.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE),242		.desired_access =  FILE_READ_DATA | FILE_READ_ATTRIBUTES |243				   FILE_READ_EA,244		.disposition = FILE_OPEN,245		.fid = pfid,246		.replay = !!(retries),247	};248 249	rc = SMB2_open_init(tcon, server,250			    &rqst[0], &oplock, &oparms, utf16_path);251	if (rc)252		goto oshr_free;253	smb2_set_next_command(tcon, &rqst[0]);254 255	memset(&qi_iov, 0, sizeof(qi_iov));256	rqst[1].rq_iov = qi_iov;257	rqst[1].rq_nvec = 1;258 259	rc = SMB2_query_info_init(tcon, server,260				  &rqst[1], COMPOUND_FID,261				  COMPOUND_FID, FILE_ALL_INFORMATION,262				  SMB2_O_INFO_FILE, 0,263				  sizeof(struct smb2_file_all_info) +264				  PATH_MAX * 2, 0, NULL);265	if (rc)266		goto oshr_free;267 268	smb2_set_related(&rqst[1]);269 270	/*271	 * Set @cfid->has_lease to true before sending out compounded request so272	 * its lease reference can be put in cached_dir_lease_break() due to a273	 * potential lease break right after the request is sent or while @cfid274	 * is still being cached.  Concurrent processes won't be to use it yet275	 * due to @cfid->time being zero.276	 */277	cfid->has_lease = true;278 279	if (retries) {280		smb2_set_replay(server, &rqst[0]);281		smb2_set_replay(server, &rqst[1]);282	}283 284	rc = compound_send_recv(xid, ses, server,285				flags, 2, rqst,286				resp_buftype, rsp_iov);287	if (rc) {288		if (rc == -EREMCHG) {289			tcon->need_reconnect = true;290			pr_warn_once("server share %s deleted\n",291				     tcon->tree_name);292		}293		goto oshr_free;294	}295	cfid->tcon = tcon;296	cfid->is_open = true;297 298	spin_lock(&cfids->cfid_list_lock);299 300	o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base;301	oparms.fid->persistent_fid = o_rsp->PersistentFileId;302	oparms.fid->volatile_fid = o_rsp->VolatileFileId;303#ifdef CONFIG_CIFS_DEBUG2304	oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId);305#endif /* CIFS_DEBUG2 */306 307 308	if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) {309		spin_unlock(&cfids->cfid_list_lock);310		rc = -EINVAL;311		goto oshr_free;312	}313 314	rc = smb2_parse_contexts(server, rsp_iov,315				 &oparms.fid->epoch,316				 oparms.fid->lease_key,317				 &oplock, NULL, NULL);318	if (rc) {319		spin_unlock(&cfids->cfid_list_lock);320		goto oshr_free;321	}322 323	rc = -EINVAL;324	if (!(oplock & SMB2_LEASE_READ_CACHING_HE)) {325		spin_unlock(&cfids->cfid_list_lock);326		goto oshr_free;327	}328	qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;329	if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info)) {330		spin_unlock(&cfids->cfid_list_lock);331		goto oshr_free;332	}333	if (!smb2_validate_and_copy_iov(334				le16_to_cpu(qi_rsp->OutputBufferOffset),335				sizeof(struct smb2_file_all_info),336				&rsp_iov[1], sizeof(struct smb2_file_all_info),337				(char *)&cfid->file_all_info))338		cfid->file_all_info_is_valid = true;339 340	cfid->time = jiffies;341	spin_unlock(&cfids->cfid_list_lock);342	/* At this point the directory handle is fully cached */343	rc = 0;344 345oshr_free:346	SMB2_open_free(&rqst[0]);347	SMB2_query_info_free(&rqst[1]);348	free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);349	free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);350	if (rc) {351		spin_lock(&cfids->cfid_list_lock);352		if (cfid->on_list) {353			list_del(&cfid->entry);354			cfid->on_list = false;355			cfids->num_entries--;356		}357		if (cfid->has_lease) {358			/*359			 * We are guaranteed to have two references at this360			 * point. One for the caller and one for a potential361			 * lease. Release the Lease-ref so that the directory362			 * will be closed when the caller closes the cached363			 * handle.364			 */365			cfid->has_lease = false;366			spin_unlock(&cfids->cfid_list_lock);367			kref_put(&cfid->refcount, smb2_close_cached_fid);368			goto out;369		}370		spin_unlock(&cfids->cfid_list_lock);371	}372out:373	if (rc) {374		if (cfid->is_open)375			SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid,376				   cfid->fid.volatile_fid);377		free_cached_dir(cfid);378	} else {379		*ret_cfid = cfid;380		atomic_inc(&tcon->num_remote_opens);381	}382	kfree(utf16_path);383 384	if (is_replayable_error(rc) &&385	    smb2_should_replay(tcon, &retries, &cur_sleep))386		goto replay_again;387 388	return rc;389}390 391int open_cached_dir_by_dentry(struct cifs_tcon *tcon,392			      struct dentry *dentry,393			      struct cached_fid **ret_cfid)394{395	struct cached_fid *cfid;396	struct cached_fids *cfids = tcon->cfids;397 398	if (cfids == NULL)399		return -ENOENT;400 401	spin_lock(&cfids->cfid_list_lock);402	list_for_each_entry(cfid, &cfids->entries, entry) {403		if (dentry && cfid->dentry == dentry) {404			cifs_dbg(FYI, "found a cached root file handle by dentry\n");405			kref_get(&cfid->refcount);406			*ret_cfid = cfid;407			spin_unlock(&cfids->cfid_list_lock);408			return 0;409		}410	}411	spin_unlock(&cfids->cfid_list_lock);412	return -ENOENT;413}414 415static void416smb2_close_cached_fid(struct kref *ref)417{418	struct cached_fid *cfid = container_of(ref, struct cached_fid,419					       refcount);420	int rc;421 422	spin_lock(&cfid->cfids->cfid_list_lock);423	if (cfid->on_list) {424		list_del(&cfid->entry);425		cfid->on_list = false;426		cfid->cfids->num_entries--;427	}428	spin_unlock(&cfid->cfids->cfid_list_lock);429 430	dput(cfid->dentry);431	cfid->dentry = NULL;432 433	if (cfid->is_open) {434		rc = SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid,435			   cfid->fid.volatile_fid);436		if (rc) /* should we retry on -EBUSY or -EAGAIN? */437			cifs_dbg(VFS, "close cached dir rc %d\n", rc);438	}439 440	free_cached_dir(cfid);441}442 443void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,444			     const char *name, struct cifs_sb_info *cifs_sb)445{446	struct cached_fid *cfid = NULL;447	int rc;448 449	rc = open_cached_dir(xid, tcon, name, cifs_sb, true, &cfid);450	if (rc) {451		cifs_dbg(FYI, "no cached dir found for rmdir(%s)\n", name);452		return;453	}454	spin_lock(&cfid->cfids->cfid_list_lock);455	if (cfid->has_lease) {456		cfid->has_lease = false;457		kref_put(&cfid->refcount, smb2_close_cached_fid);458	}459	spin_unlock(&cfid->cfids->cfid_list_lock);460	close_cached_dir(cfid);461}462 463 464void close_cached_dir(struct cached_fid *cfid)465{466	kref_put(&cfid->refcount, smb2_close_cached_fid);467}468 469/*470 * Called from cifs_kill_sb when we unmount a share471 */472void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)473{474	struct rb_root *root = &cifs_sb->tlink_tree;475	struct rb_node *node;476	struct cached_fid *cfid;477	struct cifs_tcon *tcon;478	struct tcon_link *tlink;479	struct cached_fids *cfids;480 481	for (node = rb_first(root); node; node = rb_next(node)) {482		tlink = rb_entry(node, struct tcon_link, tl_rbnode);483		tcon = tlink_tcon(tlink);484		if (IS_ERR(tcon))485			continue;486		cfids = tcon->cfids;487		if (cfids == NULL)488			continue;489		list_for_each_entry(cfid, &cfids->entries, entry) {490			dput(cfid->dentry);491			cfid->dentry = NULL;492		}493	}494}495 496/*497 * Invalidate all cached dirs when a TCON has been reset498 * due to a session loss.499 */500void invalidate_all_cached_dirs(struct cifs_tcon *tcon)501{502	struct cached_fids *cfids = tcon->cfids;503	struct cached_fid *cfid, *q;504	LIST_HEAD(entry);505 506	if (cfids == NULL)507		return;508 509	spin_lock(&cfids->cfid_list_lock);510	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {511		list_move(&cfid->entry, &entry);512		cfids->num_entries--;513		cfid->is_open = false;514		cfid->on_list = false;515		/* To prevent race with smb2_cached_lease_break() */516		kref_get(&cfid->refcount);517	}518	spin_unlock(&cfids->cfid_list_lock);519 520	list_for_each_entry_safe(cfid, q, &entry, entry) {521		list_del(&cfid->entry);522		cancel_work_sync(&cfid->lease_break);523		if (cfid->has_lease) {524			/*525			 * We lease was never cancelled from the server so we526			 * need to drop the reference.527			 */528			spin_lock(&cfids->cfid_list_lock);529			cfid->has_lease = false;530			spin_unlock(&cfids->cfid_list_lock);531			kref_put(&cfid->refcount, smb2_close_cached_fid);532		}533		/* Drop the extra reference opened above*/534		kref_put(&cfid->refcount, smb2_close_cached_fid);535	}536}537 538static void539smb2_cached_lease_break(struct work_struct *work)540{541	struct cached_fid *cfid = container_of(work,542				struct cached_fid, lease_break);543 544	spin_lock(&cfid->cfids->cfid_list_lock);545	cfid->has_lease = false;546	spin_unlock(&cfid->cfids->cfid_list_lock);547	kref_put(&cfid->refcount, smb2_close_cached_fid);548}549 550int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])551{552	struct cached_fids *cfids = tcon->cfids;553	struct cached_fid *cfid;554 555	if (cfids == NULL)556		return false;557 558	spin_lock(&cfids->cfid_list_lock);559	list_for_each_entry(cfid, &cfids->entries, entry) {560		if (cfid->has_lease &&561		    !memcmp(lease_key,562			    cfid->fid.lease_key,563			    SMB2_LEASE_KEY_SIZE)) {564			cfid->time = 0;565			/*566			 * We found a lease remove it from the list567			 * so no threads can access it.568			 */569			list_del(&cfid->entry);570			cfid->on_list = false;571			cfids->num_entries--;572 573			queue_work(cifsiod_wq,574				   &cfid->lease_break);575			spin_unlock(&cfids->cfid_list_lock);576			return true;577		}578	}579	spin_unlock(&cfids->cfid_list_lock);580	return false;581}582 583static struct cached_fid *init_cached_dir(const char *path)584{585	struct cached_fid *cfid;586 587	cfid = kzalloc(sizeof(*cfid), GFP_ATOMIC);588	if (!cfid)589		return NULL;590	cfid->path = kstrdup(path, GFP_ATOMIC);591	if (!cfid->path) {592		kfree(cfid);593		return NULL;594	}595 596	INIT_WORK(&cfid->lease_break, smb2_cached_lease_break);597	INIT_LIST_HEAD(&cfid->entry);598	INIT_LIST_HEAD(&cfid->dirents.entries);599	mutex_init(&cfid->dirents.de_mutex);600	spin_lock_init(&cfid->fid_lock);601	kref_init(&cfid->refcount);602	return cfid;603}604 605static void free_cached_dir(struct cached_fid *cfid)606{607	struct cached_dirent *dirent, *q;608 609	dput(cfid->dentry);610	cfid->dentry = NULL;611 612	/*613	 * Delete all cached dirent names614	 */615	list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) {616		list_del(&dirent->entry);617		kfree(dirent->name);618		kfree(dirent);619	}620 621	kfree(cfid->path);622	cfid->path = NULL;623	kfree(cfid);624}625 626static void cfids_laundromat_worker(struct work_struct *work)627{628	struct cached_fids *cfids;629	struct cached_fid *cfid, *q;630	LIST_HEAD(entry);631 632	cfids = container_of(work, struct cached_fids, laundromat_work.work);633 634	spin_lock(&cfids->cfid_list_lock);635	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {636		if (cfid->time &&637		    time_after(jiffies, cfid->time + HZ * dir_cache_timeout)) {638			cfid->on_list = false;639			list_move(&cfid->entry, &entry);640			cfids->num_entries--;641			/* To prevent race with smb2_cached_lease_break() */642			kref_get(&cfid->refcount);643		}644	}645	spin_unlock(&cfids->cfid_list_lock);646 647	list_for_each_entry_safe(cfid, q, &entry, entry) {648		list_del(&cfid->entry);649		/*650		 * Cancel and wait for the work to finish in case we are racing651		 * with it.652		 */653		cancel_work_sync(&cfid->lease_break);654		if (cfid->has_lease) {655			/*656			 * Our lease has not yet been cancelled from the server657			 * so we need to drop the reference.658			 */659			spin_lock(&cfids->cfid_list_lock);660			cfid->has_lease = false;661			spin_unlock(&cfids->cfid_list_lock);662			kref_put(&cfid->refcount, smb2_close_cached_fid);663		}664		/* Drop the extra reference opened above */665		kref_put(&cfid->refcount, smb2_close_cached_fid);666	}667	queue_delayed_work(cifsiod_wq, &cfids->laundromat_work,668			   dir_cache_timeout * HZ);669}670 671struct cached_fids *init_cached_dirs(void)672{673	struct cached_fids *cfids;674 675	cfids = kzalloc(sizeof(*cfids), GFP_KERNEL);676	if (!cfids)677		return NULL;678	spin_lock_init(&cfids->cfid_list_lock);679	INIT_LIST_HEAD(&cfids->entries);680 681	INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker);682	queue_delayed_work(cifsiod_wq, &cfids->laundromat_work,683			   dir_cache_timeout * HZ);684 685	return cfids;686}687 688/*689 * Called from tconInfoFree when we are tearing down the tcon.690 * There are no active users or open files/directories at this point.691 */692void free_cached_dirs(struct cached_fids *cfids)693{694	struct cached_fid *cfid, *q;695	LIST_HEAD(entry);696 697	if (cfids == NULL)698		return;699 700	cancel_delayed_work_sync(&cfids->laundromat_work);701 702	spin_lock(&cfids->cfid_list_lock);703	list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {704		cfid->on_list = false;705		cfid->is_open = false;706		list_move(&cfid->entry, &entry);707	}708	spin_unlock(&cfids->cfid_list_lock);709 710	list_for_each_entry_safe(cfid, q, &entry, entry) {711		list_del(&cfid->entry);712		free_cached_dir(cfid);713	}714 715	kfree(cfids);716}717