// SPDX-License-Identifier: GPL-2.0 /* * DFS referral cache routines * * Copyright (c) 2018-2019 Paulo Alcantara <[email protected]> */ #include <linux/jhash.h> #include <linux/ktime.h> #include <linux/slab.h> #include <linux/proc_fs.h> #include <linux/nls.h> #include <linux/workqueue.h> #include <linux/uuid.h> #include "cifsglob.h" #include "smb2pdu.h" #include "smb2proto.h" #include "cifsproto.h" #include "cifs_debug.h" #include "cifs_unicode.h" #include "smb2glob.h" #include "dns_resolve.h" #include "dfs.h" #include "dfs_cache.h" #define CACHE_HTABLE_SIZE … #define CACHE_MAX_ENTRIES … #define CACHE_MIN_TTL … #define CACHE_DEFAULT_TTL … struct cache_dfs_tgt { … }; struct cache_entry { … }; static struct kmem_cache *cache_slab __read_mostly; struct workqueue_struct *dfscache_wq; atomic_t dfs_cache_ttl; static struct nls_table *cache_cp; /* * Number of entries in the cache */ static atomic_t cache_count; static struct hlist_head cache_htable[CACHE_HTABLE_SIZE]; static DECLARE_RWSEM(htable_rw_lock); /** * dfs_cache_canonical_path - get a canonical DFS path * * @path: DFS path * @cp: codepage * @remap: mapping type * * Return canonical path if success, otherwise error. */ char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap) { … } static inline bool cache_entry_expired(const struct cache_entry *ce) { … } static inline void free_tgts(struct cache_entry *ce) { … } static inline void flush_cache_ent(struct cache_entry *ce) { … } static void flush_cache_ents(void) { … } /* * dfs cache /proc file */ static int dfscache_proc_show(struct seq_file *m, void *v) { … } static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { … } static int dfscache_proc_open(struct inode *inode, struct file *file) { … } const struct proc_ops dfscache_proc_ops = …; #ifdef CONFIG_CIFS_DEBUG2 static inline void dump_tgts(const struct cache_entry *ce) { … } static inline void dump_ce(const struct cache_entry *ce) { … } static inline void dump_refs(const struct dfs_info3_param *refs, int numrefs) { … } #else #define dump_tgts … #define dump_ce … #define dump_refs … #endif /** * dfs_cache_init - Initialize DFS referral cache. * * Return zero if initialized successfully, otherwise non-zero. */ int dfs_cache_init(void) { … } static int cache_entry_hash(const void *data, int size, unsigned int *hash) { … } /* Return target hint of a DFS cache entry */ static inline char *get_tgt_name(const struct cache_entry *ce) { … } /* Return expire time out of a new entry's TTL */ static inline struct timespec64 get_expire_time(int ttl) { … } /* Allocate a new DFS target */ static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed) { … } /* * Copy DFS referral information to a cache entry and conditionally update * target hint. */ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, struct cache_entry *ce, const char *tgthint) { … } /* Allocate a new cache entry */ static struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs) { … } static void remove_oldest_entry_locked(void) { … } /* Add a new DFS cache entry */ static struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs) { … } /* Check if two DFS paths are equal. @s1 and @s2 are expected to be in @cache_cp's charset */ static bool dfs_path_equal(const char *s1, int len1, const char *s2, int len2) { … } static struct cache_entry *__lookup_cache_entry(const char *path, unsigned int hash, int len) { … } /* * 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. */ static struct cache_entry *lookup_cache_entry(const char *path) { … } /** * dfs_cache_destroy - destroy DFS referral cache */ void dfs_cache_destroy(void) { … } /* Update a cache entry with the new referral in @refs */ static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_info3_param *refs, int numrefs) { … } static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const char *path, struct dfs_info3_param **refs, int *numrefs) { … } /* * 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. */ static struct cache_entry *cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path, bool force_refresh) { … } /* * Set up a DFS referral from a given cache entry. * * Must be called with htable_rw_lock held. */ static int setup_referral(const char *path, struct cache_entry *ce, struct dfs_info3_param *ref, const char *target) { … } /* Return target list of a DFS cache entry */ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) { … } /** * 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(const unsigned int xid, struct cifs_ses *ses, const struct nls_table *cp, int remap, const char *path, struct dfs_info3_param *ref, struct dfs_cache_tgt_list *tgt_list) { … } /** * 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(const char *path, struct dfs_info3_param *ref, struct dfs_cache_tgt_list *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(const char *path, const struct dfs_cache_tgt_iterator *it) { … } /** * 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(const char *path, const struct dfs_cache_tgt_iterator *it, struct dfs_info3_param *ref) { … } /* Extract share from DFS target and return a pointer to prefix path or NULL */ static const char *parse_target_share(const char *target, char **share) { … } /** * dfs_cache_get_tgt_share - parse a DFS target * * @path: DFS full path * @it: DFS target iterator. * @share: tree name. * @prefix: prefix path. * * Return zero if target was parsed correctly, otherwise non-zero. */ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share, char **prefix) { … } static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2) { … } /* * Mark dfs tcon for reconnecting when the currently connected tcon does not match any of the new * target shares in @refs. */ static void mark_for_reconnect_if_needed(struct TCP_Server_Info *server, const char *path, struct dfs_cache_tgt_list *old_tl, struct dfs_cache_tgt_list *new_tl) { … } static bool is_ses_good(struct cifs_ses *ses) { … } /* Refresh dfs referral of @ses and mark it for reconnect if needed */ static void __refresh_ses_referral(struct cifs_ses *ses, bool force_refresh) { … } static inline void refresh_ses_referral(struct cifs_ses *ses) { … } static inline void force_refresh_ses_referral(struct cifs_ses *ses) { … } /** * 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) { … } /* Refresh all DFS referrals related to DFS tcon */ void dfs_cache_refresh(struct work_struct *work) { … }