diff options
Diffstat (limited to 'fs/cifs/dfs_cache.c')
-rw-r--r-- | fs/cifs/dfs_cache.c | 276 |
1 files changed, 50 insertions, 226 deletions
diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c index e70915ad7541..43ad1176dcb9 100644 --- a/fs/cifs/dfs_cache.c +++ b/fs/cifs/dfs_cache.c @@ -83,27 +83,6 @@ static void refresh_cache_worker(struct work_struct *work); static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker); -static void get_ipc_unc(const char *ref_path, char *ipc, size_t ipclen) -{ - const char *host; - size_t len; - - extract_unc_hostname(ref_path, &host, &len); - scnprintf(ipc, ipclen, "\\\\%.*s\\IPC$", (int)len, host); -} - -static struct cifs_ses *find_ipc_from_server_path(struct cifs_ses **ses, const char *path) -{ - char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0}; - - get_ipc_unc(path, unc, sizeof(unc)); - for (; *ses; ses++) { - if (!strcasecmp(unc, (*ses)->tcon_ipc->tree_name)) - return *ses; - } - return ERR_PTR(-ENOENT); -} - static void __mount_group_release(struct mount_group *mg) { int i; @@ -760,8 +739,6 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const int rc; int i; - cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path); - *refs = NULL; *numrefs = 0; @@ -770,6 +747,7 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const if (unlikely(!cache_cp)) return -EINVAL; + cifs_dbg(FYI, "%s: ipc=%s referral=%s\n", __func__, ses->tcon_ipc->tree_name, path); rc = ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp, NO_MAP_UNI_RSVD); if (!rc) { @@ -1104,26 +1082,23 @@ out_free_path: * * Return zero if the target hint was updated successfully, otherwise non-zero. */ -int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it) +void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it) { - int rc; - struct cache_entry *ce; struct cache_dfs_tgt *t; + struct cache_entry *ce; - if (!it) - return -EINVAL; + if (!path || !it) + return; cifs_dbg(FYI, "%s: path: %s\n", __func__, path); - down_write(&htable_rw_lock); + if (!down_write_trylock(&htable_rw_lock)) + return; ce = lookup_cache_entry(path); - if (IS_ERR(ce)) { - rc = PTR_ERR(ce); + if (IS_ERR(ce)) goto out_unlock; - } - rc = 0; t = ce->tgthint; if (unlikely(!strcasecmp(it->it_name, t->name))) @@ -1140,7 +1115,6 @@ int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_ out_unlock: up_write(&htable_rw_lock); - return rc; } /** @@ -1314,8 +1288,7 @@ static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, c char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0}; const char *host; size_t hostlen; - char *ip = NULL; - struct sockaddr sa; + struct sockaddr_storage ss; bool match; int rc; @@ -1330,23 +1303,17 @@ static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, c extract_unc_hostname(s1, &host, &hostlen); scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host); - rc = dns_resolve_server_name_to_ip(unc, &ip, NULL); + rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL); if (rc < 0) { cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n", __func__, (int)hostlen, host); return true; } - if (!cifs_convert_address(&sa, ip, strlen(ip))) { - cifs_dbg(VFS, "%s: failed to convert address \'%s\'. skip address matching.\n", - __func__, ip); - } else { - cifs_server_lock(server); - match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, &sa); - cifs_server_unlock(server); - } + cifs_server_lock(server); + match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss); + cifs_server_unlock(server); - kfree(ip); return match; } @@ -1373,23 +1340,19 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach } /* Refresh dfs referral of tcon and mark it for reconnect if needed */ -static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon, - bool force_refresh) +static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_refresh) { - struct cifs_ses *ses; - struct cache_entry *ce; + struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); + struct cifs_ses *ses = CIFS_DFS_ROOT_SES(tcon->ses); + struct cifs_tcon *ipc = ses->tcon_ipc; struct dfs_info3_param *refs = NULL; - int numrefs = 0; bool needs_refresh = false; - struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl); - int rc = 0; + struct cache_entry *ce; unsigned int xid; + int numrefs = 0; + int rc = 0; - ses = find_ipc_from_server_path(sessions, path); - if (IS_ERR(ses)) { - cifs_dbg(FYI, "%s: could not find ipc session\n", __func__); - return PTR_ERR(ses); - } + xid = get_xid(); down_read(&htable_rw_lock); ce = lookup_cache_entry(path); @@ -1406,12 +1369,17 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c goto out; } - xid = get_xid(); - rc = get_dfs_referral(xid, ses, path, &refs, &numrefs); - free_xid(xid); + spin_lock(&ipc->tc_lock); + if (ses->ses_status != SES_GOOD || ipc->status != TID_GOOD) { + spin_unlock(&ipc->tc_lock); + cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", __func__); + goto out; + } + spin_unlock(&ipc->tc_lock); - /* Create or update a cache entry with the new referral */ + rc = get_dfs_referral(xid, ses, path, &refs, &numrefs); if (!rc) { + /* Create or update a cache entry with the new referral */ dump_refs(refs, numrefs); down_write(&htable_rw_lock); @@ -1426,24 +1394,20 @@ static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct c } out: + free_xid(xid); dfs_cache_free_tgts(&tl); free_dfs_info_array(refs, numrefs); return rc; } -static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh) +static int refresh_tcon(struct cifs_tcon *tcon, bool force_refresh) { struct TCP_Server_Info *server = tcon->ses->server; mutex_lock(&server->refpath_lock); - if (server->origin_fullpath) { - if (server->leaf_fullpath && strcasecmp(server->leaf_fullpath, - server->origin_fullpath)) - __refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, force_refresh); - __refresh_tcon(server->origin_fullpath + 1, sessions, tcon, force_refresh); - } + if (server->leaf_fullpath) + __refresh_tcon(server->leaf_fullpath + 1, tcon, force_refresh); mutex_unlock(&server->refpath_lock); - return 0; } @@ -1461,9 +1425,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb) { struct cifs_tcon *tcon; struct TCP_Server_Info *server; - struct mount_group *mg; - struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL}; - int rc; if (!cifs_sb || !cifs_sb->master_tlink) return -EINVAL; @@ -1480,21 +1441,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb) cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__); return -EINVAL; } - - mutex_lock(&mount_group_list_lock); - mg = find_mount_group_locked(&cifs_sb->dfs_mount_id); - if (IS_ERR(mg)) { - mutex_unlock(&mount_group_list_lock); - cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__); - return PTR_ERR(mg); - } - kref_get(&mg->refcount); - mutex_unlock(&mount_group_list_lock); - - spin_lock(&mg->lock); - memcpy(&sessions, mg->sessions, mg->num_sessions * sizeof(mg->sessions[0])); - spin_unlock(&mg->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). @@ -1505,42 +1451,38 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb) * that have different prefix paths. */ cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; - rc = refresh_tcon(sessions, tcon, true); - kref_put(&mg->refcount, mount_group_release); - return rc; + return refresh_tcon(tcon, true); } /* - * Refresh all active dfs mounts regardless of whether they are in cache or not. - * (cache can be cleared) + * Worker that will refresh DFS cache from all active mounts based on lowest TTL value + * from a DFS referral. */ -static void refresh_mounts(struct cifs_ses **sessions) +static void refresh_cache_worker(struct work_struct *work) { struct TCP_Server_Info *server; - struct cifs_ses *ses; struct cifs_tcon *tcon, *ntcon; struct list_head tcons; + struct cifs_ses *ses; INIT_LIST_HEAD(&tcons); spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { - spin_lock(&server->srv_lock); - if (!server->is_dfs_conn) { - spin_unlock(&server->srv_lock); + if (!server->leaf_fullpath) continue; - } - spin_unlock(&server->srv_lock); list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { + if (ses->tcon_ipc) { + ses->ses_count++; + list_add_tail(&ses->tcon_ipc->ulist, &tcons); + } list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { - spin_lock(&tcon->tc_lock); - if (!tcon->ipc && !tcon->need_reconnect) { + if (!tcon->ipc) { tcon->tc_count++; list_add_tail(&tcon->ulist, &tcons); } - spin_unlock(&tcon->tc_lock); } } } @@ -1552,132 +1494,14 @@ static void refresh_mounts(struct cifs_ses **sessions) list_del_init(&tcon->ulist); mutex_lock(&server->refpath_lock); - if (server->origin_fullpath) { - if (server->leaf_fullpath && strcasecmp(server->leaf_fullpath, - server->origin_fullpath)) - __refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false); - __refresh_tcon(server->origin_fullpath + 1, sessions, tcon, false); - } + if (server->leaf_fullpath) + __refresh_tcon(server->leaf_fullpath + 1, tcon, false); mutex_unlock(&server->refpath_lock); - cifs_put_tcon(tcon); - } -} - -static void refresh_cache(struct cifs_ses **sessions) -{ - int i; - struct cifs_ses *ses; - unsigned int xid; - char *ref_paths[CACHE_MAX_ENTRIES]; - int count = 0; - struct cache_entry *ce; - - /* - * Refresh all cached entries. Get all new referrals outside critical section to avoid - * starvation while performing SMB2 IOCTL on broken or slow connections. - - * The cache entries may cover more paths than the active mounts - * (e.g. domain-based DFS referrals or multi tier DFS setups). - */ - down_read(&htable_rw_lock); - for (i = 0; i < CACHE_HTABLE_SIZE; i++) { - struct hlist_head *l = &cache_htable[i]; - - hlist_for_each_entry(ce, l, hlist) { - if (count == ARRAY_SIZE(ref_paths)) - goto out_unlock; - if (hlist_unhashed(&ce->hlist) || !cache_entry_expired(ce) || - IS_ERR(find_ipc_from_server_path(sessions, ce->path))) - continue; - ref_paths[count++] = kstrdup(ce->path, GFP_ATOMIC); - } - } - -out_unlock: - up_read(&htable_rw_lock); - - for (i = 0; i < count; i++) { - char *path = ref_paths[i]; - struct dfs_info3_param *refs = NULL; - int numrefs = 0; - int rc = 0; - - if (!path) - continue; - - ses = find_ipc_from_server_path(sessions, path); - if (IS_ERR(ses)) - goto next_referral; - - xid = get_xid(); - rc = get_dfs_referral(xid, ses, path, &refs, &numrefs); - free_xid(xid); - - if (!rc) { - down_write(&htable_rw_lock); - ce = lookup_cache_entry(path); - /* - * We need to re-check it because other tasks might have it deleted or - * updated. - */ - if (!IS_ERR(ce) && cache_entry_expired(ce)) - update_cache_entry_locked(ce, refs, numrefs); - up_write(&htable_rw_lock); - } - -next_referral: - kfree(path); - free_dfs_info_array(refs, numrefs); - } -} - -/* - * Worker that will refresh DFS cache and active mounts based on lowest TTL value from a DFS - * referral. - */ -static void refresh_cache_worker(struct work_struct *work) -{ - struct list_head mglist; - struct mount_group *mg, *tmp_mg; - struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL}; - int max_sessions = ARRAY_SIZE(sessions) - 1; - int i = 0, count; - - INIT_LIST_HEAD(&mglist); - - /* Get refereces of mount groups */ - mutex_lock(&mount_group_list_lock); - list_for_each_entry(mg, &mount_group_list, list) { - kref_get(&mg->refcount); - list_add(&mg->refresh_list, &mglist); - } - mutex_unlock(&mount_group_list_lock); - - /* Fill in local array with an NULL-terminated list of all referral server sessions */ - list_for_each_entry(mg, &mglist, refresh_list) { - if (i >= max_sessions) - break; - - spin_lock(&mg->lock); - if (i + mg->num_sessions > max_sessions) - count = max_sessions - i; + if (tcon->ipc) + cifs_put_smb_ses(tcon->ses); else - count = mg->num_sessions; - memcpy(&sessions[i], mg->sessions, count * sizeof(mg->sessions[0])); - spin_unlock(&mg->lock); - i += count; - } - - if (sessions[0]) { - /* Refresh all active mounts and cached entries */ - refresh_mounts(sessions); - refresh_cache(sessions); - } - - list_for_each_entry_safe(mg, tmp_mg, &mglist, refresh_list) { - list_del_init(&mg->refresh_list); - kref_put(&mg->refcount, mount_group_release); + cifs_put_tcon(tcon); } spin_lock(&cache_ttl_lock); |