/** * dfs_cache_init - Initialize DFS referral cache. * * Return zero if initialized successfully, otherwise non-zero.
*/ int dfs_cache_init(void)
{ int rc; int i;
dfscache_wq = alloc_workqueue("cifs-dfscache",
WQ_UNBOUND|WQ_FREEZABLE|WQ_MEM_RECLAIM,
0); if (!dfscache_wq) return -ENOMEM;
/* Allocate a new cache entry */ staticstruct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs)
{ struct cache_entry *ce; int rc;
ce = kmem_cache_zalloc(cache_slab, GFP_KERNEL); if (!ce) return ERR_PTR(-ENOMEM);
rc = copy_ref_data(refs, numrefs, ce, NULL); if (rc) {
kfree(ce->path);
kmem_cache_free(cache_slab, ce);
ce = ERR_PTR(rc);
} return ce;
}
/* Remove all referrals that have a single target or oldest entry */ staticvoid purge_cache(void)
{ int i; struct cache_entry *ce; struct cache_entry *oldest = NULL;
for (i = 0; i < CACHE_HTABLE_SIZE; i++) { struct hlist_head *l = &cache_htable[i]; struct hlist_node *n;
hlist_for_each_entry_safe(ce, n, l, hlist) { if (hlist_unhashed(&ce->hlist)) continue; if (ce->numtgts == 1)
flush_cache_ent(ce); elseif (!oldest ||
timespec64_compare(&ce->etime,
&oldest->etime) < 0)
oldest = ce;
}
}
if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES && oldest)
flush_cache_ent(oldest);
}
/* Add a new DFS cache entry */ staticstruct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)
{ int rc; struct cache_entry *ce; unsignedint hash; int ttl;
WARN_ON(!rwsem_is_locked(&htable_rw_lock));
if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) {
cifs_dbg(FYI, "%s: reached max cache size (%d)\n", __func__, CACHE_MAX_ENTRIES);
purge_cache();
}
rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash); if (rc) return ERR_PTR(rc);
ce = alloc_cache_entry(refs, numrefs); if (IS_ERR(ce)) return ce;
/* Check if two DFS paths are equal. @s1 and @s2 are expected to be in @cache_cp's charset */ staticbool dfs_path_equal(constchar *s1, int len1, constchar *s2, int len2)
{ int i, l1, l2; wchar_t c1, c2;
if (len1 != len2) returnfalse;
for (i = 0; i < len1; i += l1) {
l1 = cache_cp->char2uni(&s1[i], len1 - i, &c1);
l2 = cache_cp->char2uni(&s2[i], len2 - i, &c2); if (unlikely(l1 < 0 && l2 < 0)) { if (s1[i] != s2[i]) returnfalse;
l1 = 1; continue;
} if (l1 != l2) returnfalse; if (cifs_toupper(c1) != cifs_toupper(c2)) returnfalse;
} returntrue;
}
/* * Find a DFS cache entry in hash table and optionally check prefix path against normalized @path. * * Use whole path components in the match. Must be called with htable_rw_lock held. * * Return cached entry if successful. * Return ERR_PTR(-ENOENT) if the entry is not found. * Return error ptr otherwise.
*/ staticstruct cache_entry *lookup_cache_entry(constchar *path)
{ struct cache_entry *ce; int cnt = 0; constchar *s = path, *e; char sep = *s; unsignedint hash; int rc;
while ((s = strchr(s, sep)) && ++cnt < 3)
s++;
if (cnt < 3) {
rc = cache_entry_hash(path, strlen(path), &hash); if (rc) return ERR_PTR(rc); return __lookup_cache_entry(path, hash, strlen(path));
} /* * Handle paths that have more than two path components and are a complete prefix of the DFS * referral request path (@path). * * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
*/
e = path + strlen(path) - 1; while (e > s) { int len;
/* skip separators */ while (e > s && *e == sep)
e--; if (e == s) break;
len = e + 1 - path;
rc = cache_entry_hash(path, len, &hash); if (rc) return ERR_PTR(rc);
ce = __lookup_cache_entry(path, hash, len); if (!IS_ERR(ce)) return ce;
/* backward until separator */ while (e > s && *e != sep)
e--;
} return ERR_PTR(-ENOENT);
}
/* Update a cache entry with the new referral in @refs */ staticint update_cache_entry_locked(struct cache_entry *ce, conststruct dfs_info3_param *refs, int numrefs)
{ struct cache_dfs_tgt *target; char *th = NULL; int rc;
WARN_ON(!rwsem_is_locked(&htable_rw_lock));
target = READ_ONCE(ce->tgthint); if (target) {
th = kstrdup(target->name, GFP_ATOMIC); if (!th) return -ENOMEM;
}
free_tgts(ce);
ce->numtgts = 0;
rc = copy_ref_data(refs, numrefs, ce, th);
kfree(th);
return rc;
}
staticint get_dfs_referral(constunsignedint xid, struct cifs_ses *ses, constchar *path, struct dfs_info3_param **refs, int *numrefs)
{ int rc; int i;
*refs = NULL;
*numrefs = 0;
if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) return -EOPNOTSUPP; if (unlikely(!cache_cp)) return -EINVAL;
for (i = 0; i < *numrefs; i++)
convert_delimiter(ref[i].path_name, '\\');
} return rc;
}
/* * Find, create or update a DFS cache entry. * * If the entry wasn't found, it will create a new one. Or if it was found but * expired, then it will update the entry accordingly. * * For interlinks, cifs_mount() and expand_dfs_referral() are supposed to * handle them properly. * * On success, return entry with acquired lock for reading, otherwise error ptr.
*/ staticstruct cache_entry *cache_refresh_path(constunsignedint xid, struct cifs_ses *ses, constchar *path, bool force_refresh)
{ struct dfs_info3_param *refs = NULL; struct cache_entry *ce; int numrefs = 0; int rc;
ce = lookup_cache_entry(path); if (!IS_ERR(ce)) { if (!force_refresh && !cache_entry_expired(ce)) return ce;
} elseif (PTR_ERR(ce) != -ENOENT) {
up_read(&htable_rw_lock); return ce;
}
/* * Unlock shared access as we don't want to hold any locks while getting * a new referral. The @ses used for performing the I/O could be * reconnecting and it acquires @htable_rw_lock to look up the dfs cache * in order to failover -- if necessary.
*/
up_read(&htable_rw_lock);
/* * Either the entry was not found, or it is expired, or it is a forced * refresh. * Request a new DFS referral in order to create or update a cache entry.
*/
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs); if (rc) {
ce = ERR_PTR(rc); goto out;
}
dump_refs(refs, numrefs);
down_write(&htable_rw_lock); /* Re-check as another task might have it added or refreshed already */
ce = lookup_cache_entry(path); if (!IS_ERR(ce)) { if (force_refresh || cache_entry_expired(ce)) {
rc = update_cache_entry_locked(ce, refs, numrefs); if (rc)
ce = ERR_PTR(rc);
}
} elseif (PTR_ERR(ce) == -ENOENT) {
ce = add_cache_entry_locked(refs, numrefs);
}
if (IS_ERR(ce)) {
up_write(&htable_rw_lock); goto out;
}
/* * Set up a DFS referral from a given cache entry. * * Must be called with htable_rw_lock held.
*/ staticint setup_referral(constchar *path, struct cache_entry *ce, struct dfs_info3_param *ref, constchar *target)
{ int rc;
cifs_dbg(FYI, "%s: set up new ref\n", __func__);
memset(ref, 0, sizeof(*ref));
ref->path_name = kstrdup(path, GFP_ATOMIC); if (!ref->path_name) return -ENOMEM;
/** * dfs_cache_find - find a DFS cache entry * * If it doesn't find the cache entry, then it will get a DFS referral * for @path and create a new entry. * * In case the cache entry exists but expired, it will get a DFS referral * for @path and then update the respective cache entry. * * These parameters are passed down to the get_dfs_refer() call if it * needs to be issued: * @xid: syscall xid * @ses: smb session to issue the request on * @cp: codepage * @remap: path character remapping type * @path: path to lookup in DFS referral cache. * * @ref: when non-NULL, store single DFS referral result in it. * @tgt_list: when non-NULL, store complete DFS target list in it. * * Return zero if the target was found, otherwise non-zero.
*/ int dfs_cache_find(constunsignedint xid, struct cifs_ses *ses, conststruct nls_table *cp, int remap, constchar *path, struct dfs_info3_param *ref, struct dfs_cache_tgt_list *tgt_list)
{ int rc; constchar *npath; struct cache_entry *ce;
npath = dfs_cache_canonical_path(path, cp, remap); if (IS_ERR(npath)) return PTR_ERR(npath);
ce = cache_refresh_path(xid, ses, npath, false); if (IS_ERR(ce)) {
rc = PTR_ERR(ce); goto out_free_path;
}
if (ref)
rc = setup_referral(path, ce, ref, get_tgt_name(ce)); else
rc = 0; if (!rc && tgt_list)
rc = get_targets(ce, tgt_list);
up_read(&htable_rw_lock);
out_free_path:
kfree(npath); return rc;
}
/** * dfs_cache_noreq_find - find a DFS cache entry without sending any requests to * the currently connected server. * * NOTE: This function will neither update a cache entry in case it was * expired, nor create a new cache entry if @path hasn't been found. It heavily * relies on an existing cache entry. * * @path: canonical DFS path to lookup in the DFS referral cache. * @ref: when non-NULL, store single DFS referral result in it. * @tgt_list: when non-NULL, store complete DFS target list in it. * * Return 0 if successful. * Return -ENOENT if the entry was not found. * Return non-zero for other errors.
*/ int dfs_cache_noreq_find(constchar *path, struct dfs_info3_param *ref, struct dfs_cache_tgt_list *tgt_list)
{ int rc; struct cache_entry *ce;
cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
down_read(&htable_rw_lock);
ce = lookup_cache_entry(path); if (IS_ERR(ce)) {
rc = PTR_ERR(ce); goto out_unlock;
}
if (ref)
rc = setup_referral(path, ce, ref, get_tgt_name(ce)); else
rc = 0; if (!rc && tgt_list)
rc = get_targets(ce, tgt_list);
/** * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry * without sending any requests to the currently connected server. * * NOTE: This function will neither update a cache entry in case it was * expired, nor create a new cache entry if @path hasn't been found. It heavily * relies on an existing cache entry. * * @path: canonical DFS path to lookup in DFS referral cache. * @it: target iterator which contains the target hint to update the cache * entry with. * * Return zero if the target hint was updated successfully, otherwise non-zero.
*/ void dfs_cache_noreq_update_tgthint(constchar *path, conststruct dfs_cache_tgt_iterator *it)
{ struct cache_dfs_tgt *t; struct cache_entry *ce;
if (!path || !it) return;
cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
down_read(&htable_rw_lock);
ce = lookup_cache_entry(path); if (IS_ERR(ce)) goto out_unlock;
t = READ_ONCE(ce->tgthint);
if (unlikely(!strcasecmp(it->it_name, t->name))) goto out_unlock;
/** * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given * target iterator (@it). * * @path: canonical DFS path to lookup in DFS referral cache. * @it: DFS target iterator. * @ref: DFS referral pointer to set up the gathered information. * * Return zero if the DFS referral was set up correctly, otherwise non-zero.
*/ int dfs_cache_get_tgt_referral(constchar *path, conststruct dfs_cache_tgt_iterator *it, struct dfs_info3_param *ref)
{ int rc; struct cache_entry *ce;
if (!it || !ref) return -EINVAL;
cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
down_read(&htable_rw_lock);
ce = lookup_cache_entry(path); if (IS_ERR(ce)) {
rc = PTR_ERR(ce); goto out_unlock;
}
/* * Resolve share's hostname and check if server address matches. Otherwise just ignore it * as we could not have upcall to resolve hostname or failed to convert ip address.
*/
rc = dns_resolve_unc(server->dns_dom, s1, (struct sockaddr *)&ss); if (rc < 0) returntrue;
ses = CIFS_DFS_ROOT_SES(ses); if (!is_ses_good(ses)) {
cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
__func__); goto out;
}
ce = cache_refresh_path(xid, ses, path, false); if (!IS_ERR(ce))
up_read(&htable_rw_lock); else
rc = PTR_ERR(ce);
out:
free_xid(xid);
}
staticint __refresh_tcon_referral(struct cifs_tcon *tcon, constchar *path, struct dfs_info3_param *refs, int numrefs, bool force_refresh)
{ struct cache_entry *ce; bool reconnect = force_refresh; int rc = 0; int i;
if (unlikely(!numrefs)) return 0;
if (force_refresh) { for (i = 0; i < numrefs; i++) { /* TODO: include prefix paths in the matching */ if (target_share_equal(tcon, refs[i].node_name)) {
reconnect = false; break;
}
}
}
down_write(&htable_rw_lock);
ce = lookup_cache_entry(path); if (!IS_ERR(ce)) { if (force_refresh || cache_entry_expired(ce))
rc = update_cache_entry_locked(ce, refs, numrefs);
} elseif (PTR_ERR(ce) == -ENOENT) {
ce = add_cache_entry_locked(refs, numrefs);
}
up_write(&htable_rw_lock);
if (IS_ERR(ce))
rc = PTR_ERR(ce); if (reconnect) {
cifs_tcon_dbg(FYI, "%s: mark for reconnect\n", __func__);
cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
} return rc;
}
/** * dfs_cache_remount_fs - remount a DFS share * * Reconfigure dfs mount by forcing a new DFS referral and if the currently cached targets do not * match any of the new targets, mark it for reconnect. * * @cifs_sb: cifs superblock. * * Return zero if remounted, otherwise non-zero.
*/ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
{ struct cifs_tcon *tcon;
if (!cifs_sb || !cifs_sb->master_tlink) return -EINVAL;
tcon = cifs_sb_master_tcon(cifs_sb);
spin_lock(&tcon->tc_lock); if (!tcon->origin_fullpath) {
spin_unlock(&tcon->tc_lock);
cifs_dbg(FYI, "%s: not a dfs mount\n", __func__); return 0;
}
spin_unlock(&tcon->tc_lock);
/* * After reconnecting to a different server, unique ids won't match anymore, so we disable * serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
*/
cifs_autodisable_serverino(cifs_sb); /* * Force the use of prefix path to support failover on DFS paths that resolve to targets * that have different prefix paths.
*/
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
refresh_tcon_referral(tcon, true); return 0;
}
/* Refresh all DFS referrals related to DFS tcon */ void dfs_cache_refresh(struct work_struct *work)
{ struct cifs_tcon *tcon; struct cifs_ses *ses;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.