Commit | Line | Data |
---|---|---|
54be1f6c PA |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * DFS referral cache routines | |
4 | * | |
5072010c | 5 | * Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de> |
54be1f6c PA |
6 | */ |
7 | ||
8 | #include <linux/rcupdate.h> | |
9 | #include <linux/rculist.h> | |
10 | #include <linux/jhash.h> | |
11 | #include <linux/ktime.h> | |
12 | #include <linux/slab.h> | |
13 | #include <linux/nls.h> | |
14 | #include <linux/workqueue.h> | |
15 | #include "cifsglob.h" | |
16 | #include "smb2pdu.h" | |
17 | #include "smb2proto.h" | |
18 | #include "cifsproto.h" | |
19 | #include "cifs_debug.h" | |
20 | #include "cifs_unicode.h" | |
21 | #include "smb2glob.h" | |
22 | ||
23 | #include "dfs_cache.h" | |
24 | ||
25 | #define DFS_CACHE_HTABLE_SIZE 32 | |
26 | #define DFS_CACHE_MAX_ENTRIES 64 | |
27 | ||
28 | #define IS_INTERLINK_SET(v) ((v) & (DFSREF_REFERRAL_SERVER | \ | |
29 | DFSREF_STORAGE_SERVER)) | |
30 | ||
31 | struct dfs_cache_tgt { | |
32 | char *t_name; | |
33 | struct list_head t_list; | |
34 | }; | |
35 | ||
36 | struct dfs_cache_entry { | |
37 | struct hlist_node ce_hlist; | |
38 | const char *ce_path; | |
39 | int ce_ttl; | |
40 | int ce_srvtype; | |
41 | int ce_flags; | |
42 | struct timespec64 ce_etime; | |
43 | int ce_path_consumed; | |
44 | int ce_numtgts; | |
45 | struct list_head ce_tlist; | |
46 | struct dfs_cache_tgt *ce_tgthint; | |
47 | struct rcu_head ce_rcu; | |
48 | }; | |
49 | ||
50 | static struct kmem_cache *dfs_cache_slab __read_mostly; | |
51 | ||
52 | struct dfs_cache_vol_info { | |
53 | char *vi_fullpath; | |
54 | struct smb_vol vi_vol; | |
5072010c | 55 | char *vi_mntdata; |
54be1f6c PA |
56 | struct list_head vi_list; |
57 | }; | |
58 | ||
59 | struct dfs_cache { | |
60 | struct mutex dc_lock; | |
61 | struct nls_table *dc_nlsc; | |
62 | struct list_head dc_vol_list; | |
63 | int dc_ttl; | |
64 | struct delayed_work dc_refresh; | |
65 | }; | |
66 | ||
67 | static struct dfs_cache dfs_cache; | |
68 | ||
69 | /* | |
70 | * Number of entries in the cache | |
71 | */ | |
72 | static size_t dfs_cache_count; | |
73 | ||
74 | static DEFINE_MUTEX(dfs_cache_list_lock); | |
75 | static struct hlist_head dfs_cache_htable[DFS_CACHE_HTABLE_SIZE]; | |
76 | ||
77 | static void refresh_cache_worker(struct work_struct *work); | |
78 | ||
79 | static inline bool is_path_valid(const char *path) | |
80 | { | |
81 | return path && (strchr(path + 1, '\\') || strchr(path + 1, '/')); | |
82 | } | |
83 | ||
84 | static inline int get_normalized_path(const char *path, char **npath) | |
85 | { | |
86 | if (*path == '\\') { | |
87 | *npath = (char *)path; | |
88 | } else { | |
89 | *npath = kstrndup(path, strlen(path), GFP_KERNEL); | |
90 | if (!*npath) | |
91 | return -ENOMEM; | |
92 | convert_delimiter(*npath, '\\'); | |
93 | } | |
94 | return 0; | |
95 | } | |
96 | ||
97 | static inline void free_normalized_path(const char *path, char *npath) | |
98 | { | |
99 | if (path != npath) | |
100 | kfree(npath); | |
101 | } | |
102 | ||
103 | static inline bool cache_entry_expired(const struct dfs_cache_entry *ce) | |
104 | { | |
105 | struct timespec64 ts; | |
106 | ||
54e4f73c | 107 | ktime_get_coarse_real_ts64(&ts); |
54be1f6c PA |
108 | return timespec64_compare(&ts, &ce->ce_etime) >= 0; |
109 | } | |
110 | ||
111 | static inline void free_tgts(struct dfs_cache_entry *ce) | |
112 | { | |
113 | struct dfs_cache_tgt *t, *n; | |
114 | ||
115 | list_for_each_entry_safe(t, n, &ce->ce_tlist, t_list) { | |
116 | list_del(&t->t_list); | |
117 | kfree(t->t_name); | |
118 | kfree(t); | |
119 | } | |
120 | } | |
121 | ||
122 | static void free_cache_entry(struct rcu_head *rcu) | |
123 | { | |
124 | struct dfs_cache_entry *ce = container_of(rcu, struct dfs_cache_entry, | |
125 | ce_rcu); | |
126 | kmem_cache_free(dfs_cache_slab, ce); | |
127 | } | |
128 | ||
129 | static inline void flush_cache_ent(struct dfs_cache_entry *ce) | |
130 | { | |
131 | if (hlist_unhashed(&ce->ce_hlist)) | |
132 | return; | |
133 | ||
134 | hlist_del_init_rcu(&ce->ce_hlist); | |
50fbc13d | 135 | kfree_const(ce->ce_path); |
54be1f6c PA |
136 | free_tgts(ce); |
137 | dfs_cache_count--; | |
138 | call_rcu(&ce->ce_rcu, free_cache_entry); | |
139 | } | |
140 | ||
141 | static void flush_cache_ents(void) | |
142 | { | |
143 | int i; | |
144 | ||
145 | rcu_read_lock(); | |
146 | for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) { | |
147 | struct hlist_head *l = &dfs_cache_htable[i]; | |
148 | struct dfs_cache_entry *ce; | |
149 | ||
150 | hlist_for_each_entry_rcu(ce, l, ce_hlist) | |
151 | flush_cache_ent(ce); | |
152 | } | |
153 | rcu_read_unlock(); | |
154 | } | |
155 | ||
156 | /* | |
157 | * dfs cache /proc file | |
158 | */ | |
159 | static int dfscache_proc_show(struct seq_file *m, void *v) | |
160 | { | |
161 | int bucket; | |
162 | struct dfs_cache_entry *ce; | |
163 | struct dfs_cache_tgt *t; | |
164 | ||
165 | seq_puts(m, "DFS cache\n---------\n"); | |
166 | ||
167 | mutex_lock(&dfs_cache_list_lock); | |
168 | ||
169 | rcu_read_lock(); | |
170 | hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { | |
171 | seq_printf(m, | |
172 | "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," | |
173 | "interlink=%s,path_consumed=%d,expired=%s\n", | |
174 | ce->ce_path, | |
175 | ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", | |
176 | ce->ce_ttl, ce->ce_etime.tv_nsec, | |
177 | IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", | |
178 | ce->ce_path_consumed, | |
179 | cache_entry_expired(ce) ? "yes" : "no"); | |
180 | ||
181 | list_for_each_entry(t, &ce->ce_tlist, t_list) { | |
182 | seq_printf(m, " %s%s\n", | |
183 | t->t_name, | |
184 | ce->ce_tgthint == t ? " (target hint)" : ""); | |
185 | } | |
186 | ||
187 | } | |
188 | rcu_read_unlock(); | |
189 | ||
190 | mutex_unlock(&dfs_cache_list_lock); | |
191 | return 0; | |
192 | } | |
193 | ||
194 | static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer, | |
195 | size_t count, loff_t *ppos) | |
196 | { | |
197 | char c; | |
198 | int rc; | |
199 | ||
200 | rc = get_user(c, buffer); | |
201 | if (rc) | |
202 | return rc; | |
203 | ||
204 | if (c != '0') | |
205 | return -EINVAL; | |
206 | ||
207 | cifs_dbg(FYI, "clearing dfs cache"); | |
208 | mutex_lock(&dfs_cache_list_lock); | |
209 | flush_cache_ents(); | |
210 | mutex_unlock(&dfs_cache_list_lock); | |
211 | ||
212 | return count; | |
213 | } | |
214 | ||
215 | static int dfscache_proc_open(struct inode *inode, struct file *file) | |
216 | { | |
217 | return single_open(file, dfscache_proc_show, NULL); | |
218 | } | |
219 | ||
220 | const struct file_operations dfscache_proc_fops = { | |
221 | .open = dfscache_proc_open, | |
222 | .read = seq_read, | |
223 | .llseek = seq_lseek, | |
224 | .release = single_release, | |
225 | .write = dfscache_proc_write, | |
226 | }; | |
227 | ||
228 | #ifdef CONFIG_CIFS_DEBUG2 | |
229 | static inline void dump_tgts(const struct dfs_cache_entry *ce) | |
230 | { | |
231 | struct dfs_cache_tgt *t; | |
232 | ||
233 | cifs_dbg(FYI, "target list:\n"); | |
234 | list_for_each_entry(t, &ce->ce_tlist, t_list) { | |
235 | cifs_dbg(FYI, " %s%s\n", t->t_name, | |
236 | ce->ce_tgthint == t ? " (target hint)" : ""); | |
237 | } | |
238 | } | |
239 | ||
240 | static inline void dump_ce(const struct dfs_cache_entry *ce) | |
241 | { | |
242 | cifs_dbg(FYI, "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," | |
243 | "interlink=%s,path_consumed=%d,expired=%s\n", ce->ce_path, | |
244 | ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ce_ttl, | |
245 | ce->ce_etime.tv_nsec, | |
246 | IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", | |
247 | ce->ce_path_consumed, | |
248 | cache_entry_expired(ce) ? "yes" : "no"); | |
249 | dump_tgts(ce); | |
250 | } | |
251 | ||
252 | static inline void dump_refs(const struct dfs_info3_param *refs, int numrefs) | |
253 | { | |
254 | int i; | |
255 | ||
256 | cifs_dbg(FYI, "DFS referrals returned by the server:\n"); | |
257 | for (i = 0; i < numrefs; i++) { | |
258 | const struct dfs_info3_param *ref = &refs[i]; | |
259 | ||
260 | cifs_dbg(FYI, | |
261 | "\n" | |
262 | "flags: 0x%x\n" | |
263 | "path_consumed: %d\n" | |
264 | "server_type: 0x%x\n" | |
265 | "ref_flag: 0x%x\n" | |
266 | "path_name: %s\n" | |
267 | "node_name: %s\n" | |
268 | "ttl: %d (%dm)\n", | |
269 | ref->flags, ref->path_consumed, ref->server_type, | |
270 | ref->ref_flag, ref->path_name, ref->node_name, | |
271 | ref->ttl, ref->ttl / 60); | |
272 | } | |
273 | } | |
274 | #else | |
275 | #define dump_tgts(e) | |
276 | #define dump_ce(e) | |
277 | #define dump_refs(r, n) | |
278 | #endif | |
279 | ||
280 | /** | |
281 | * dfs_cache_init - Initialize DFS referral cache. | |
282 | * | |
283 | * Return zero if initialized successfully, otherwise non-zero. | |
284 | */ | |
285 | int dfs_cache_init(void) | |
286 | { | |
287 | int i; | |
288 | ||
289 | dfs_cache_slab = kmem_cache_create("cifs_dfs_cache", | |
290 | sizeof(struct dfs_cache_entry), 0, | |
291 | SLAB_HWCACHE_ALIGN, NULL); | |
292 | if (!dfs_cache_slab) | |
293 | return -ENOMEM; | |
294 | ||
295 | for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) | |
296 | INIT_HLIST_HEAD(&dfs_cache_htable[i]); | |
297 | ||
298 | INIT_LIST_HEAD(&dfs_cache.dc_vol_list); | |
299 | mutex_init(&dfs_cache.dc_lock); | |
300 | INIT_DELAYED_WORK(&dfs_cache.dc_refresh, refresh_cache_worker); | |
301 | dfs_cache.dc_ttl = -1; | |
302 | dfs_cache.dc_nlsc = load_nls_default(); | |
303 | ||
304 | cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); | |
305 | return 0; | |
306 | } | |
307 | ||
308 | static inline unsigned int cache_entry_hash(const void *data, int size) | |
309 | { | |
310 | unsigned int h; | |
311 | ||
312 | h = jhash(data, size, 0); | |
313 | return h & (DFS_CACHE_HTABLE_SIZE - 1); | |
314 | } | |
315 | ||
316 | /* Check whether second path component of @path is SYSVOL or NETLOGON */ | |
317 | static inline bool is_sysvol_or_netlogon(const char *path) | |
318 | { | |
319 | const char *s; | |
320 | char sep = path[0]; | |
321 | ||
322 | s = strchr(path + 1, sep) + 1; | |
323 | return !strncasecmp(s, "sysvol", strlen("sysvol")) || | |
324 | !strncasecmp(s, "netlogon", strlen("netlogon")); | |
325 | } | |
326 | ||
327 | /* Return target hint of a DFS cache entry */ | |
328 | static inline char *get_tgt_name(const struct dfs_cache_entry *ce) | |
329 | { | |
330 | struct dfs_cache_tgt *t = ce->ce_tgthint; | |
331 | ||
332 | return t ? t->t_name : ERR_PTR(-ENOENT); | |
333 | } | |
334 | ||
335 | /* Return expire time out of a new entry's TTL */ | |
336 | static inline struct timespec64 get_expire_time(int ttl) | |
337 | { | |
338 | struct timespec64 ts = { | |
339 | .tv_sec = ttl, | |
340 | .tv_nsec = 0, | |
341 | }; | |
54e4f73c | 342 | struct timespec64 now; |
54be1f6c | 343 | |
54e4f73c SR |
344 | ktime_get_coarse_real_ts64(&now); |
345 | return timespec64_add(now, ts); | |
54be1f6c PA |
346 | } |
347 | ||
348 | /* Allocate a new DFS target */ | |
349 | static inline struct dfs_cache_tgt *alloc_tgt(const char *name) | |
350 | { | |
351 | struct dfs_cache_tgt *t; | |
352 | ||
353 | t = kmalloc(sizeof(*t), GFP_KERNEL); | |
354 | if (!t) | |
355 | return ERR_PTR(-ENOMEM); | |
356 | t->t_name = kstrndup(name, strlen(name), GFP_KERNEL); | |
357 | if (!t->t_name) { | |
358 | kfree(t); | |
359 | return ERR_PTR(-ENOMEM); | |
360 | } | |
361 | INIT_LIST_HEAD(&t->t_list); | |
362 | return t; | |
363 | } | |
364 | ||
365 | /* | |
366 | * Copy DFS referral information to a cache entry and conditionally update | |
367 | * target hint. | |
368 | */ | |
369 | static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, | |
370 | struct dfs_cache_entry *ce, const char *tgthint) | |
371 | { | |
372 | int i; | |
373 | ||
374 | ce->ce_ttl = refs[0].ttl; | |
375 | ce->ce_etime = get_expire_time(ce->ce_ttl); | |
376 | ce->ce_srvtype = refs[0].server_type; | |
377 | ce->ce_flags = refs[0].ref_flag; | |
378 | ce->ce_path_consumed = refs[0].path_consumed; | |
379 | ||
380 | for (i = 0; i < numrefs; i++) { | |
381 | struct dfs_cache_tgt *t; | |
382 | ||
383 | t = alloc_tgt(refs[i].node_name); | |
384 | if (IS_ERR(t)) { | |
385 | free_tgts(ce); | |
386 | return PTR_ERR(t); | |
387 | } | |
388 | if (tgthint && !strcasecmp(t->t_name, tgthint)) { | |
389 | list_add(&t->t_list, &ce->ce_tlist); | |
390 | tgthint = NULL; | |
391 | } else { | |
392 | list_add_tail(&t->t_list, &ce->ce_tlist); | |
393 | } | |
394 | ce->ce_numtgts++; | |
395 | } | |
396 | ||
397 | ce->ce_tgthint = list_first_entry_or_null(&ce->ce_tlist, | |
398 | struct dfs_cache_tgt, t_list); | |
399 | ||
400 | return 0; | |
401 | } | |
402 | ||
403 | /* Allocate a new cache entry */ | |
404 | static struct dfs_cache_entry * | |
405 | alloc_cache_entry(const char *path, const struct dfs_info3_param *refs, | |
406 | int numrefs) | |
407 | { | |
408 | struct dfs_cache_entry *ce; | |
409 | int rc; | |
410 | ||
411 | ce = kmem_cache_zalloc(dfs_cache_slab, GFP_KERNEL); | |
412 | if (!ce) | |
413 | return ERR_PTR(-ENOMEM); | |
414 | ||
415 | ce->ce_path = kstrdup_const(path, GFP_KERNEL); | |
416 | if (!ce->ce_path) { | |
3e80be01 | 417 | kmem_cache_free(dfs_cache_slab, ce); |
54be1f6c PA |
418 | return ERR_PTR(-ENOMEM); |
419 | } | |
420 | INIT_HLIST_NODE(&ce->ce_hlist); | |
421 | INIT_LIST_HEAD(&ce->ce_tlist); | |
422 | ||
423 | rc = copy_ref_data(refs, numrefs, ce, NULL); | |
424 | if (rc) { | |
50fbc13d | 425 | kfree_const(ce->ce_path); |
3e80be01 | 426 | kmem_cache_free(dfs_cache_slab, ce); |
54be1f6c PA |
427 | ce = ERR_PTR(rc); |
428 | } | |
429 | return ce; | |
430 | } | |
431 | ||
432 | static void remove_oldest_entry(void) | |
433 | { | |
434 | int bucket; | |
435 | struct dfs_cache_entry *ce; | |
436 | struct dfs_cache_entry *to_del = NULL; | |
437 | ||
438 | rcu_read_lock(); | |
439 | hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { | |
440 | if (!to_del || timespec64_compare(&ce->ce_etime, | |
441 | &to_del->ce_etime) < 0) | |
442 | to_del = ce; | |
443 | } | |
444 | if (!to_del) { | |
445 | cifs_dbg(FYI, "%s: no entry to remove", __func__); | |
446 | goto out; | |
447 | } | |
448 | cifs_dbg(FYI, "%s: removing entry", __func__); | |
449 | dump_ce(to_del); | |
450 | flush_cache_ent(to_del); | |
451 | out: | |
452 | rcu_read_unlock(); | |
453 | } | |
454 | ||
455 | /* Add a new DFS cache entry */ | |
456 | static inline struct dfs_cache_entry * | |
457 | add_cache_entry(unsigned int hash, const char *path, | |
458 | const struct dfs_info3_param *refs, int numrefs) | |
459 | { | |
460 | struct dfs_cache_entry *ce; | |
461 | ||
462 | ce = alloc_cache_entry(path, refs, numrefs); | |
463 | if (IS_ERR(ce)) | |
464 | return ce; | |
465 | ||
466 | hlist_add_head_rcu(&ce->ce_hlist, &dfs_cache_htable[hash]); | |
467 | ||
468 | mutex_lock(&dfs_cache.dc_lock); | |
469 | if (dfs_cache.dc_ttl < 0) { | |
470 | dfs_cache.dc_ttl = ce->ce_ttl; | |
471 | queue_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, | |
472 | dfs_cache.dc_ttl * HZ); | |
473 | } else { | |
474 | dfs_cache.dc_ttl = min_t(int, dfs_cache.dc_ttl, ce->ce_ttl); | |
475 | mod_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, | |
476 | dfs_cache.dc_ttl * HZ); | |
477 | } | |
478 | mutex_unlock(&dfs_cache.dc_lock); | |
479 | ||
480 | return ce; | |
481 | } | |
482 | ||
483 | static struct dfs_cache_entry *__find_cache_entry(unsigned int hash, | |
484 | const char *path) | |
485 | { | |
486 | struct dfs_cache_entry *ce; | |
487 | bool found = false; | |
488 | ||
489 | rcu_read_lock(); | |
490 | hlist_for_each_entry_rcu(ce, &dfs_cache_htable[hash], ce_hlist) { | |
491 | if (!strcasecmp(path, ce->ce_path)) { | |
492 | #ifdef CONFIG_CIFS_DEBUG2 | |
493 | char *name = get_tgt_name(ce); | |
494 | ||
06f2fca7 | 495 | if (IS_ERR(name)) { |
54be1f6c PA |
496 | rcu_read_unlock(); |
497 | return ERR_CAST(name); | |
498 | } | |
499 | cifs_dbg(FYI, "%s: cache hit\n", __func__); | |
500 | cifs_dbg(FYI, "%s: target hint: %s\n", __func__, name); | |
501 | #endif | |
502 | found = true; | |
503 | break; | |
504 | } | |
505 | } | |
506 | rcu_read_unlock(); | |
507 | return found ? ce : ERR_PTR(-ENOENT); | |
508 | } | |
509 | ||
510 | /* | |
511 | * Find a DFS cache entry in hash table and optionally check prefix path against | |
512 | * @path. | |
513 | * Use whole path components in the match. | |
514 | * Return ERR_PTR(-ENOENT) if the entry is not found. | |
515 | */ | |
516 | static inline struct dfs_cache_entry *find_cache_entry(const char *path, | |
517 | unsigned int *hash) | |
518 | { | |
519 | *hash = cache_entry_hash(path, strlen(path)); | |
520 | return __find_cache_entry(*hash, path); | |
521 | } | |
522 | ||
523 | static inline void destroy_slab_cache(void) | |
524 | { | |
525 | rcu_barrier(); | |
526 | kmem_cache_destroy(dfs_cache_slab); | |
527 | } | |
528 | ||
529 | static inline void free_vol(struct dfs_cache_vol_info *vi) | |
530 | { | |
531 | list_del(&vi->vi_list); | |
532 | kfree(vi->vi_fullpath); | |
5072010c | 533 | kfree(vi->vi_mntdata); |
54be1f6c PA |
534 | cifs_cleanup_volume_info_contents(&vi->vi_vol); |
535 | kfree(vi); | |
536 | } | |
537 | ||
538 | static inline void free_vol_list(void) | |
539 | { | |
540 | struct dfs_cache_vol_info *vi, *nvi; | |
541 | ||
542 | list_for_each_entry_safe(vi, nvi, &dfs_cache.dc_vol_list, vi_list) | |
543 | free_vol(vi); | |
544 | } | |
545 | ||
546 | /** | |
547 | * dfs_cache_destroy - destroy DFS referral cache | |
548 | */ | |
549 | void dfs_cache_destroy(void) | |
550 | { | |
551 | cancel_delayed_work_sync(&dfs_cache.dc_refresh); | |
552 | unload_nls(dfs_cache.dc_nlsc); | |
553 | free_vol_list(); | |
554 | mutex_destroy(&dfs_cache.dc_lock); | |
555 | ||
556 | flush_cache_ents(); | |
557 | destroy_slab_cache(); | |
558 | mutex_destroy(&dfs_cache_list_lock); | |
559 | ||
560 | cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__); | |
561 | } | |
562 | ||
563 | static inline struct dfs_cache_entry * | |
564 | __update_cache_entry(const char *path, const struct dfs_info3_param *refs, | |
565 | int numrefs) | |
566 | { | |
567 | int rc; | |
568 | unsigned int h; | |
569 | struct dfs_cache_entry *ce; | |
570 | char *s, *th = NULL; | |
571 | ||
572 | ce = find_cache_entry(path, &h); | |
573 | if (IS_ERR(ce)) | |
574 | return ce; | |
575 | ||
576 | if (ce->ce_tgthint) { | |
577 | s = ce->ce_tgthint->t_name; | |
578 | th = kstrndup(s, strlen(s), GFP_KERNEL); | |
579 | if (!th) | |
580 | return ERR_PTR(-ENOMEM); | |
581 | } | |
582 | ||
583 | free_tgts(ce); | |
584 | ce->ce_numtgts = 0; | |
585 | ||
586 | rc = copy_ref_data(refs, numrefs, ce, th); | |
587 | kfree(th); | |
588 | ||
589 | if (rc) | |
590 | ce = ERR_PTR(rc); | |
591 | ||
592 | return ce; | |
593 | } | |
594 | ||
595 | /* Update an expired cache entry by getting a new DFS referral from server */ | |
596 | static struct dfs_cache_entry * | |
597 | update_cache_entry(const unsigned int xid, struct cifs_ses *ses, | |
598 | const struct nls_table *nls_codepage, int remap, | |
599 | const char *path, struct dfs_cache_entry *ce) | |
600 | { | |
601 | int rc; | |
602 | struct dfs_info3_param *refs = NULL; | |
603 | int numrefs = 0; | |
604 | ||
605 | cifs_dbg(FYI, "%s: update expired cache entry\n", __func__); | |
606 | /* | |
607 | * Check if caller provided enough parameters to update an expired | |
608 | * entry. | |
609 | */ | |
610 | if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) | |
611 | return ERR_PTR(-ETIME); | |
612 | if (unlikely(!nls_codepage)) | |
613 | return ERR_PTR(-ETIME); | |
614 | ||
615 | cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, path); | |
616 | ||
617 | rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs, &numrefs, | |
618 | nls_codepage, remap); | |
619 | if (rc) | |
620 | ce = ERR_PTR(rc); | |
621 | else | |
622 | ce = __update_cache_entry(path, refs, numrefs); | |
623 | ||
624 | dump_refs(refs, numrefs); | |
625 | free_dfs_info_array(refs, numrefs); | |
626 | ||
627 | return ce; | |
628 | } | |
629 | ||
630 | /* | |
631 | * Find, create or update a DFS cache entry. | |
632 | * | |
633 | * If the entry wasn't found, it will create a new one. Or if it was found but | |
634 | * expired, then it will update the entry accordingly. | |
635 | * | |
636 | * For interlinks, __cifs_dfs_mount() and expand_dfs_referral() are supposed to | |
637 | * handle them properly. | |
638 | */ | |
639 | static struct dfs_cache_entry * | |
640 | do_dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, | |
641 | const struct nls_table *nls_codepage, int remap, | |
642 | const char *path, bool noreq) | |
643 | { | |
644 | int rc; | |
645 | unsigned int h; | |
646 | struct dfs_cache_entry *ce; | |
647 | struct dfs_info3_param *nrefs; | |
648 | int numnrefs; | |
649 | ||
650 | cifs_dbg(FYI, "%s: search path: %s\n", __func__, path); | |
651 | ||
652 | ce = find_cache_entry(path, &h); | |
653 | if (IS_ERR(ce)) { | |
654 | cifs_dbg(FYI, "%s: cache miss\n", __func__); | |
655 | /* | |
656 | * If @noreq is set, no requests will be sent to the server for | |
657 | * either updating or getting a new DFS referral. | |
658 | */ | |
659 | if (noreq) | |
660 | return ce; | |
661 | /* | |
662 | * No cache entry was found, so check for valid parameters that | |
663 | * will be required to get a new DFS referral and then create a | |
664 | * new cache entry. | |
665 | */ | |
666 | if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) { | |
667 | ce = ERR_PTR(-EOPNOTSUPP); | |
668 | return ce; | |
669 | } | |
670 | if (unlikely(!nls_codepage)) { | |
671 | ce = ERR_PTR(-EINVAL); | |
672 | return ce; | |
673 | } | |
674 | ||
675 | nrefs = NULL; | |
676 | numnrefs = 0; | |
677 | ||
678 | cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, | |
679 | path); | |
680 | ||
681 | rc = ses->server->ops->get_dfs_refer(xid, ses, path, &nrefs, | |
682 | &numnrefs, nls_codepage, | |
683 | remap); | |
684 | if (rc) { | |
685 | ce = ERR_PTR(rc); | |
686 | return ce; | |
687 | } | |
688 | ||
689 | dump_refs(nrefs, numnrefs); | |
690 | ||
691 | cifs_dbg(FYI, "%s: new cache entry\n", __func__); | |
692 | ||
693 | if (dfs_cache_count >= DFS_CACHE_MAX_ENTRIES) { | |
694 | cifs_dbg(FYI, "%s: reached max cache size (%d)", | |
695 | __func__, DFS_CACHE_MAX_ENTRIES); | |
696 | remove_oldest_entry(); | |
697 | } | |
698 | ce = add_cache_entry(h, path, nrefs, numnrefs); | |
699 | free_dfs_info_array(nrefs, numnrefs); | |
700 | ||
701 | if (IS_ERR(ce)) | |
702 | return ce; | |
703 | ||
704 | dfs_cache_count++; | |
705 | } | |
706 | ||
707 | dump_ce(ce); | |
708 | ||
709 | /* Just return the found cache entry in case @noreq is set */ | |
710 | if (noreq) | |
711 | return ce; | |
712 | ||
713 | if (cache_entry_expired(ce)) { | |
714 | cifs_dbg(FYI, "%s: expired cache entry\n", __func__); | |
715 | ce = update_cache_entry(xid, ses, nls_codepage, remap, path, | |
716 | ce); | |
717 | if (IS_ERR(ce)) { | |
718 | cifs_dbg(FYI, "%s: failed to update expired entry\n", | |
719 | __func__); | |
720 | } | |
721 | } | |
722 | return ce; | |
723 | } | |
724 | ||
725 | /* Set up a new DFS referral from a given cache entry */ | |
726 | static int setup_ref(const char *path, const struct dfs_cache_entry *ce, | |
727 | struct dfs_info3_param *ref, const char *tgt) | |
728 | { | |
729 | int rc; | |
730 | ||
731 | cifs_dbg(FYI, "%s: set up new ref\n", __func__); | |
732 | ||
733 | memset(ref, 0, sizeof(*ref)); | |
734 | ||
735 | ref->path_name = kstrndup(path, strlen(path), GFP_KERNEL); | |
736 | if (!ref->path_name) | |
737 | return -ENOMEM; | |
738 | ||
739 | ref->path_consumed = ce->ce_path_consumed; | |
740 | ||
741 | ref->node_name = kstrndup(tgt, strlen(tgt), GFP_KERNEL); | |
742 | if (!ref->node_name) { | |
743 | rc = -ENOMEM; | |
744 | goto err_free_path; | |
745 | } | |
746 | ||
747 | ref->ttl = ce->ce_ttl; | |
748 | ref->server_type = ce->ce_srvtype; | |
749 | ref->ref_flag = ce->ce_flags; | |
750 | ||
751 | return 0; | |
752 | ||
753 | err_free_path: | |
754 | kfree(ref->path_name); | |
755 | ref->path_name = NULL; | |
756 | return rc; | |
757 | } | |
758 | ||
759 | /* Return target list of a DFS cache entry */ | |
760 | static int get_tgt_list(const struct dfs_cache_entry *ce, | |
761 | struct dfs_cache_tgt_list *tl) | |
762 | { | |
763 | int rc; | |
764 | struct list_head *head = &tl->tl_list; | |
765 | struct dfs_cache_tgt *t; | |
766 | struct dfs_cache_tgt_iterator *it, *nit; | |
767 | ||
768 | memset(tl, 0, sizeof(*tl)); | |
769 | INIT_LIST_HEAD(head); | |
770 | ||
771 | list_for_each_entry(t, &ce->ce_tlist, t_list) { | |
772 | it = kzalloc(sizeof(*it), GFP_KERNEL); | |
773 | if (!it) { | |
774 | rc = -ENOMEM; | |
775 | goto err_free_it; | |
776 | } | |
777 | ||
778 | it->it_name = kstrndup(t->t_name, strlen(t->t_name), | |
779 | GFP_KERNEL); | |
780 | if (!it->it_name) { | |
c715f89c | 781 | kfree(it); |
54be1f6c PA |
782 | rc = -ENOMEM; |
783 | goto err_free_it; | |
784 | } | |
785 | ||
786 | if (ce->ce_tgthint == t) | |
787 | list_add(&it->it_list, head); | |
788 | else | |
789 | list_add_tail(&it->it_list, head); | |
790 | } | |
791 | tl->tl_numtgts = ce->ce_numtgts; | |
792 | ||
793 | return 0; | |
794 | ||
795 | err_free_it: | |
796 | list_for_each_entry_safe(it, nit, head, it_list) { | |
797 | kfree(it->it_name); | |
798 | kfree(it); | |
799 | } | |
800 | return rc; | |
801 | } | |
802 | ||
803 | /** | |
804 | * dfs_cache_find - find a DFS cache entry | |
805 | * | |
806 | * If it doesn't find the cache entry, then it will get a DFS referral | |
807 | * for @path and create a new entry. | |
808 | * | |
809 | * In case the cache entry exists but expired, it will get a DFS referral | |
810 | * for @path and then update the respective cache entry. | |
811 | * | |
812 | * These parameters are passed down to the get_dfs_refer() call if it | |
813 | * needs to be issued: | |
814 | * @xid: syscall xid | |
815 | * @ses: smb session to issue the request on | |
816 | * @nls_codepage: charset conversion | |
817 | * @remap: path character remapping type | |
818 | * @path: path to lookup in DFS referral cache. | |
819 | * | |
820 | * @ref: when non-NULL, store single DFS referral result in it. | |
821 | * @tgt_list: when non-NULL, store complete DFS target list in it. | |
822 | * | |
823 | * Return zero if the target was found, otherwise non-zero. | |
824 | */ | |
825 | int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, | |
826 | const struct nls_table *nls_codepage, int remap, | |
827 | const char *path, struct dfs_info3_param *ref, | |
828 | struct dfs_cache_tgt_list *tgt_list) | |
829 | { | |
830 | int rc; | |
831 | char *npath; | |
832 | struct dfs_cache_entry *ce; | |
833 | ||
834 | if (unlikely(!is_path_valid(path))) | |
835 | return -EINVAL; | |
836 | ||
837 | rc = get_normalized_path(path, &npath); | |
838 | if (rc) | |
839 | return rc; | |
840 | ||
841 | mutex_lock(&dfs_cache_list_lock); | |
842 | ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); | |
843 | if (!IS_ERR(ce)) { | |
844 | if (ref) | |
845 | rc = setup_ref(path, ce, ref, get_tgt_name(ce)); | |
846 | else | |
847 | rc = 0; | |
848 | if (!rc && tgt_list) | |
849 | rc = get_tgt_list(ce, tgt_list); | |
850 | } else { | |
851 | rc = PTR_ERR(ce); | |
852 | } | |
853 | mutex_unlock(&dfs_cache_list_lock); | |
854 | free_normalized_path(path, npath); | |
855 | return rc; | |
856 | } | |
857 | ||
858 | /** | |
859 | * dfs_cache_noreq_find - find a DFS cache entry without sending any requests to | |
860 | * the currently connected server. | |
861 | * | |
862 | * NOTE: This function will neither update a cache entry in case it was | |
863 | * expired, nor create a new cache entry if @path hasn't been found. It heavily | |
864 | * relies on an existing cache entry. | |
865 | * | |
866 | * @path: path to lookup in the DFS referral cache. | |
867 | * @ref: when non-NULL, store single DFS referral result in it. | |
868 | * @tgt_list: when non-NULL, store complete DFS target list in it. | |
869 | * | |
870 | * Return 0 if successful. | |
871 | * Return -ENOENT if the entry was not found. | |
872 | * Return non-zero for other errors. | |
873 | */ | |
874 | int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, | |
875 | struct dfs_cache_tgt_list *tgt_list) | |
876 | { | |
877 | int rc; | |
878 | char *npath; | |
879 | struct dfs_cache_entry *ce; | |
880 | ||
881 | if (unlikely(!is_path_valid(path))) | |
882 | return -EINVAL; | |
883 | ||
884 | rc = get_normalized_path(path, &npath); | |
885 | if (rc) | |
886 | return rc; | |
887 | ||
888 | mutex_lock(&dfs_cache_list_lock); | |
889 | ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); | |
890 | if (IS_ERR(ce)) { | |
891 | rc = PTR_ERR(ce); | |
892 | goto out; | |
893 | } | |
894 | ||
895 | if (ref) | |
896 | rc = setup_ref(path, ce, ref, get_tgt_name(ce)); | |
897 | else | |
898 | rc = 0; | |
899 | if (!rc && tgt_list) | |
900 | rc = get_tgt_list(ce, tgt_list); | |
901 | out: | |
902 | mutex_unlock(&dfs_cache_list_lock); | |
903 | free_normalized_path(path, npath); | |
904 | return rc; | |
905 | } | |
906 | ||
907 | /** | |
908 | * dfs_cache_update_tgthint - update target hint of a DFS cache entry | |
909 | * | |
910 | * If it doesn't find the cache entry, then it will get a DFS referral for @path | |
911 | * and create a new entry. | |
912 | * | |
913 | * In case the cache entry exists but expired, it will get a DFS referral | |
914 | * for @path and then update the respective cache entry. | |
915 | * | |
916 | * @xid: syscall id | |
917 | * @ses: smb session | |
918 | * @nls_codepage: charset conversion | |
919 | * @remap: type of character remapping for paths | |
920 | * @path: path to lookup in DFS referral cache. | |
921 | * @it: DFS target iterator | |
922 | * | |
923 | * Return zero if the target hint was updated successfully, otherwise non-zero. | |
924 | */ | |
925 | int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses, | |
926 | const struct nls_table *nls_codepage, int remap, | |
927 | const char *path, | |
928 | const struct dfs_cache_tgt_iterator *it) | |
929 | { | |
930 | int rc; | |
931 | char *npath; | |
932 | struct dfs_cache_entry *ce; | |
933 | struct dfs_cache_tgt *t; | |
934 | ||
935 | if (unlikely(!is_path_valid(path))) | |
936 | return -EINVAL; | |
937 | ||
938 | rc = get_normalized_path(path, &npath); | |
939 | if (rc) | |
940 | return rc; | |
941 | ||
942 | cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); | |
943 | ||
944 | mutex_lock(&dfs_cache_list_lock); | |
945 | ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); | |
946 | if (IS_ERR(ce)) { | |
947 | rc = PTR_ERR(ce); | |
948 | goto out; | |
949 | } | |
950 | ||
951 | rc = 0; | |
952 | ||
953 | t = ce->ce_tgthint; | |
954 | ||
955 | if (likely(!strcasecmp(it->it_name, t->t_name))) | |
956 | goto out; | |
957 | ||
958 | list_for_each_entry(t, &ce->ce_tlist, t_list) { | |
959 | if (!strcasecmp(t->t_name, it->it_name)) { | |
960 | ce->ce_tgthint = t; | |
961 | cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, | |
962 | it->it_name); | |
963 | break; | |
964 | } | |
965 | } | |
966 | ||
967 | out: | |
968 | mutex_unlock(&dfs_cache_list_lock); | |
969 | free_normalized_path(path, npath); | |
970 | return rc; | |
971 | } | |
972 | ||
973 | /** | |
974 | * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry | |
975 | * without sending any requests to the currently connected server. | |
976 | * | |
977 | * NOTE: This function will neither update a cache entry in case it was | |
978 | * expired, nor create a new cache entry if @path hasn't been found. It heavily | |
979 | * relies on an existing cache entry. | |
980 | * | |
981 | * @path: path to lookup in DFS referral cache. | |
982 | * @it: target iterator which contains the target hint to update the cache | |
983 | * entry with. | |
984 | * | |
985 | * Return zero if the target hint was updated successfully, otherwise non-zero. | |
986 | */ | |
987 | int dfs_cache_noreq_update_tgthint(const char *path, | |
988 | const struct dfs_cache_tgt_iterator *it) | |
989 | { | |
990 | int rc; | |
991 | char *npath; | |
992 | struct dfs_cache_entry *ce; | |
993 | struct dfs_cache_tgt *t; | |
994 | ||
995 | if (unlikely(!is_path_valid(path)) || !it) | |
996 | return -EINVAL; | |
997 | ||
998 | rc = get_normalized_path(path, &npath); | |
999 | if (rc) | |
1000 | return rc; | |
1001 | ||
1002 | cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); | |
1003 | ||
1004 | mutex_lock(&dfs_cache_list_lock); | |
1005 | ||
1006 | ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); | |
1007 | if (IS_ERR(ce)) { | |
1008 | rc = PTR_ERR(ce); | |
1009 | goto out; | |
1010 | } | |
1011 | ||
1012 | rc = 0; | |
1013 | ||
1014 | t = ce->ce_tgthint; | |
1015 | ||
1016 | if (unlikely(!strcasecmp(it->it_name, t->t_name))) | |
1017 | goto out; | |
1018 | ||
1019 | list_for_each_entry(t, &ce->ce_tlist, t_list) { | |
1020 | if (!strcasecmp(t->t_name, it->it_name)) { | |
1021 | ce->ce_tgthint = t; | |
1022 | cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, | |
1023 | it->it_name); | |
1024 | break; | |
1025 | } | |
1026 | } | |
1027 | ||
1028 | out: | |
1029 | mutex_unlock(&dfs_cache_list_lock); | |
1030 | free_normalized_path(path, npath); | |
1031 | return rc; | |
1032 | } | |
1033 | ||
1034 | /** | |
1035 | * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given | |
1036 | * target iterator (@it). | |
1037 | * | |
1038 | * @path: path to lookup in DFS referral cache. | |
1039 | * @it: DFS target iterator. | |
1040 | * @ref: DFS referral pointer to set up the gathered information. | |
1041 | * | |
1042 | * Return zero if the DFS referral was set up correctly, otherwise non-zero. | |
1043 | */ | |
1044 | int dfs_cache_get_tgt_referral(const char *path, | |
1045 | const struct dfs_cache_tgt_iterator *it, | |
1046 | struct dfs_info3_param *ref) | |
1047 | { | |
1048 | int rc; | |
1049 | char *npath; | |
1050 | struct dfs_cache_entry *ce; | |
1051 | unsigned int h; | |
1052 | ||
1053 | if (!it || !ref) | |
1054 | return -EINVAL; | |
1055 | if (unlikely(!is_path_valid(path))) | |
1056 | return -EINVAL; | |
1057 | ||
1058 | rc = get_normalized_path(path, &npath); | |
1059 | if (rc) | |
1060 | return rc; | |
1061 | ||
1062 | cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); | |
1063 | ||
1064 | mutex_lock(&dfs_cache_list_lock); | |
1065 | ||
1066 | ce = find_cache_entry(npath, &h); | |
1067 | if (IS_ERR(ce)) { | |
1068 | rc = PTR_ERR(ce); | |
1069 | goto out; | |
1070 | } | |
1071 | ||
1072 | cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name); | |
1073 | ||
1074 | rc = setup_ref(path, ce, ref, it->it_name); | |
1075 | ||
1076 | out: | |
1077 | mutex_unlock(&dfs_cache_list_lock); | |
1078 | free_normalized_path(path, npath); | |
1079 | return rc; | |
1080 | } | |
1081 | ||
1082 | static int dup_vol(struct smb_vol *vol, struct smb_vol *new) | |
1083 | { | |
1084 | memcpy(new, vol, sizeof(*new)); | |
1085 | ||
1086 | if (vol->username) { | |
1087 | new->username = kstrndup(vol->username, strlen(vol->username), | |
1088 | GFP_KERNEL); | |
1089 | if (!new->username) | |
1090 | return -ENOMEM; | |
1091 | } | |
1092 | if (vol->password) { | |
1093 | new->password = kstrndup(vol->password, strlen(vol->password), | |
1094 | GFP_KERNEL); | |
1095 | if (!new->password) | |
1096 | goto err_free_username; | |
1097 | } | |
1098 | if (vol->UNC) { | |
1099 | cifs_dbg(FYI, "%s: vol->UNC: %s\n", __func__, vol->UNC); | |
1100 | new->UNC = kstrndup(vol->UNC, strlen(vol->UNC), GFP_KERNEL); | |
1101 | if (!new->UNC) | |
1102 | goto err_free_password; | |
1103 | } | |
1104 | if (vol->domainname) { | |
1105 | new->domainname = kstrndup(vol->domainname, | |
1106 | strlen(vol->domainname), GFP_KERNEL); | |
1107 | if (!new->domainname) | |
1108 | goto err_free_unc; | |
1109 | } | |
1110 | if (vol->iocharset) { | |
1111 | new->iocharset = kstrndup(vol->iocharset, | |
1112 | strlen(vol->iocharset), GFP_KERNEL); | |
1113 | if (!new->iocharset) | |
1114 | goto err_free_domainname; | |
1115 | } | |
1116 | if (vol->prepath) { | |
1117 | cifs_dbg(FYI, "%s: vol->prepath: %s\n", __func__, vol->prepath); | |
1118 | new->prepath = kstrndup(vol->prepath, strlen(vol->prepath), | |
1119 | GFP_KERNEL); | |
1120 | if (!new->prepath) | |
1121 | goto err_free_iocharset; | |
1122 | } | |
1123 | ||
1124 | return 0; | |
1125 | ||
1126 | err_free_iocharset: | |
1127 | kfree(new->iocharset); | |
1128 | err_free_domainname: | |
1129 | kfree(new->domainname); | |
1130 | err_free_unc: | |
1131 | kfree(new->UNC); | |
1132 | err_free_password: | |
34bca9bb | 1133 | kzfree(new->password); |
54be1f6c PA |
1134 | err_free_username: |
1135 | kfree(new->username); | |
1136 | kfree(new); | |
1137 | return -ENOMEM; | |
1138 | } | |
1139 | ||
1140 | /** | |
1141 | * dfs_cache_add_vol - add a cifs volume during mount() that will be handled by | |
1142 | * DFS cache refresh worker. | |
1143 | * | |
5072010c | 1144 | * @mntdata: mount data. |
54be1f6c PA |
1145 | * @vol: cifs volume. |
1146 | * @fullpath: origin full path. | |
1147 | * | |
1148 | * Return zero if volume was set up correctly, otherwise non-zero. | |
1149 | */ | |
5072010c | 1150 | int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, const char *fullpath) |
54be1f6c PA |
1151 | { |
1152 | int rc; | |
1153 | struct dfs_cache_vol_info *vi; | |
1154 | ||
5072010c | 1155 | if (!vol || !fullpath || !mntdata) |
54be1f6c PA |
1156 | return -EINVAL; |
1157 | ||
1158 | cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); | |
1159 | ||
1160 | vi = kzalloc(sizeof(*vi), GFP_KERNEL); | |
1161 | if (!vi) | |
1162 | return -ENOMEM; | |
1163 | ||
1164 | vi->vi_fullpath = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); | |
1165 | if (!vi->vi_fullpath) { | |
1166 | rc = -ENOMEM; | |
1167 | goto err_free_vi; | |
1168 | } | |
1169 | ||
1170 | rc = dup_vol(vol, &vi->vi_vol); | |
1171 | if (rc) | |
1172 | goto err_free_fullpath; | |
1173 | ||
5072010c PAS |
1174 | vi->vi_mntdata = mntdata; |
1175 | ||
54be1f6c PA |
1176 | mutex_lock(&dfs_cache.dc_lock); |
1177 | list_add_tail(&vi->vi_list, &dfs_cache.dc_vol_list); | |
1178 | mutex_unlock(&dfs_cache.dc_lock); | |
1179 | return 0; | |
1180 | ||
1181 | err_free_fullpath: | |
1182 | kfree(vi->vi_fullpath); | |
1183 | err_free_vi: | |
1184 | kfree(vi); | |
1185 | return rc; | |
1186 | } | |
1187 | ||
1188 | static inline struct dfs_cache_vol_info *find_vol(const char *fullpath) | |
1189 | { | |
1190 | struct dfs_cache_vol_info *vi; | |
1191 | ||
1192 | list_for_each_entry(vi, &dfs_cache.dc_vol_list, vi_list) { | |
1193 | cifs_dbg(FYI, "%s: vi->vi_fullpath: %s\n", __func__, | |
1194 | vi->vi_fullpath); | |
1195 | if (!strcasecmp(vi->vi_fullpath, fullpath)) | |
1196 | return vi; | |
1197 | } | |
1198 | return ERR_PTR(-ENOENT); | |
1199 | } | |
1200 | ||
1201 | /** | |
1202 | * dfs_cache_update_vol - update vol info in DFS cache after failover | |
1203 | * | |
1204 | * @fullpath: fullpath to look up in volume list. | |
1205 | * @server: TCP ses pointer. | |
1206 | * | |
1207 | * Return zero if volume was updated, otherwise non-zero. | |
1208 | */ | |
1209 | int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server) | |
1210 | { | |
1211 | int rc; | |
1212 | struct dfs_cache_vol_info *vi; | |
1213 | ||
1214 | if (!fullpath || !server) | |
1215 | return -EINVAL; | |
1216 | ||
1217 | cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); | |
1218 | ||
1219 | mutex_lock(&dfs_cache.dc_lock); | |
1220 | ||
1221 | vi = find_vol(fullpath); | |
1222 | if (IS_ERR(vi)) { | |
1223 | rc = PTR_ERR(vi); | |
1224 | goto out; | |
1225 | } | |
1226 | ||
1227 | cifs_dbg(FYI, "%s: updating volume info\n", __func__); | |
1228 | memcpy(&vi->vi_vol.dstaddr, &server->dstaddr, | |
1229 | sizeof(vi->vi_vol.dstaddr)); | |
1230 | rc = 0; | |
1231 | ||
1232 | out: | |
1233 | mutex_unlock(&dfs_cache.dc_lock); | |
1234 | return rc; | |
1235 | } | |
1236 | ||
1237 | /** | |
1238 | * dfs_cache_del_vol - remove volume info in DFS cache during umount() | |
1239 | * | |
1240 | * @fullpath: fullpath to look up in volume list. | |
1241 | */ | |
1242 | void dfs_cache_del_vol(const char *fullpath) | |
1243 | { | |
1244 | struct dfs_cache_vol_info *vi; | |
1245 | ||
1246 | if (!fullpath || !*fullpath) | |
1247 | return; | |
1248 | ||
1249 | cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); | |
1250 | ||
1251 | mutex_lock(&dfs_cache.dc_lock); | |
1252 | vi = find_vol(fullpath); | |
1253 | if (!IS_ERR(vi)) | |
1254 | free_vol(vi); | |
1255 | mutex_unlock(&dfs_cache.dc_lock); | |
1256 | } | |
1257 | ||
1258 | /* Get all tcons that are within a DFS namespace and can be refreshed */ | |
1259 | static void get_tcons(struct TCP_Server_Info *server, struct list_head *head) | |
1260 | { | |
1261 | struct cifs_ses *ses; | |
1262 | struct cifs_tcon *tcon; | |
1263 | ||
1264 | INIT_LIST_HEAD(head); | |
1265 | ||
1266 | spin_lock(&cifs_tcp_ses_lock); | |
1267 | list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { | |
1268 | list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { | |
1269 | if (!tcon->need_reconnect && !tcon->need_reopen_files && | |
1270 | tcon->dfs_path) { | |
1271 | tcon->tc_count++; | |
1272 | list_add_tail(&tcon->ulist, head); | |
1273 | } | |
1274 | } | |
1275 | if (ses->tcon_ipc && !ses->tcon_ipc->need_reconnect && | |
1276 | ses->tcon_ipc->dfs_path) { | |
1277 | list_add_tail(&ses->tcon_ipc->ulist, head); | |
1278 | } | |
1279 | } | |
1280 | spin_unlock(&cifs_tcp_ses_lock); | |
1281 | } | |
1282 | ||
5072010c PAS |
1283 | static inline bool is_dfs_link(const char *path) |
1284 | { | |
1285 | char *s; | |
1286 | ||
1287 | s = strchr(path + 1, '\\'); | |
1288 | if (!s) | |
1289 | return false; | |
1290 | return !!strchr(s + 1, '\\'); | |
1291 | } | |
1292 | ||
1293 | static inline char *get_dfs_root(const char *path) | |
1294 | { | |
1295 | char *s, *npath; | |
1296 | ||
1297 | s = strchr(path + 1, '\\'); | |
1298 | if (!s) | |
1299 | return ERR_PTR(-EINVAL); | |
1300 | ||
1301 | s = strchr(s + 1, '\\'); | |
1302 | if (!s) | |
1303 | return ERR_PTR(-EINVAL); | |
1304 | ||
1305 | npath = kstrndup(path, s - path, GFP_KERNEL); | |
1306 | if (!npath) | |
1307 | return ERR_PTR(-ENOMEM); | |
1308 | ||
1309 | return npath; | |
1310 | } | |
1311 | ||
1312 | /* Find root SMB session out of a DFS link path */ | |
1313 | static struct cifs_ses *find_root_ses(struct dfs_cache_vol_info *vi, | |
1314 | struct cifs_tcon *tcon, const char *path) | |
1315 | { | |
1316 | char *rpath; | |
1317 | int rc; | |
1318 | struct dfs_info3_param ref = {0}; | |
1319 | char *mdata = NULL, *devname = NULL; | |
1320 | bool is_smb3 = tcon->ses->server->vals->header_preamble_size == 0; | |
1321 | struct TCP_Server_Info *server; | |
1322 | struct cifs_ses *ses; | |
1323 | struct smb_vol vol; | |
1324 | ||
1325 | rpath = get_dfs_root(path); | |
1326 | if (IS_ERR(rpath)) | |
1327 | return ERR_CAST(rpath); | |
1328 | ||
1329 | memset(&vol, 0, sizeof(vol)); | |
1330 | ||
1331 | rc = dfs_cache_noreq_find(rpath, &ref, NULL); | |
1332 | if (rc) { | |
1333 | ses = ERR_PTR(rc); | |
1334 | goto out; | |
1335 | } | |
1336 | ||
1337 | mdata = cifs_compose_mount_options(vi->vi_mntdata, rpath, &ref, | |
1338 | &devname); | |
1339 | free_dfs_info_param(&ref); | |
1340 | ||
1341 | if (IS_ERR(mdata)) { | |
1342 | ses = ERR_CAST(mdata); | |
1343 | mdata = NULL; | |
1344 | goto out; | |
1345 | } | |
1346 | ||
1347 | rc = cifs_setup_volume_info(&vol, mdata, devname, is_smb3); | |
1348 | kfree(devname); | |
1349 | ||
1350 | if (rc) { | |
1351 | ses = ERR_PTR(rc); | |
1352 | goto out; | |
1353 | } | |
1354 | ||
1355 | server = cifs_find_tcp_session(&vol); | |
1356 | if (IS_ERR_OR_NULL(server)) { | |
1357 | ses = ERR_PTR(-EHOSTDOWN); | |
1358 | goto out; | |
1359 | } | |
1360 | if (server->tcpStatus != CifsGood) { | |
1361 | cifs_put_tcp_session(server, 0); | |
1362 | ses = ERR_PTR(-EHOSTDOWN); | |
1363 | goto out; | |
1364 | } | |
1365 | ||
1366 | ses = cifs_get_smb_ses(server, &vol); | |
1367 | ||
1368 | out: | |
1369 | cifs_cleanup_volume_info_contents(&vol); | |
1370 | kfree(mdata); | |
1371 | kfree(rpath); | |
1372 | ||
1373 | return ses; | |
1374 | } | |
1375 | ||
54be1f6c | 1376 | /* Refresh DFS cache entry from a given tcon */ |
5072010c PAS |
1377 | static void do_refresh_tcon(struct dfs_cache *dc, struct dfs_cache_vol_info *vi, |
1378 | struct cifs_tcon *tcon) | |
54be1f6c PA |
1379 | { |
1380 | int rc = 0; | |
1381 | unsigned int xid; | |
1382 | char *path, *npath; | |
1383 | unsigned int h; | |
1384 | struct dfs_cache_entry *ce; | |
1385 | struct dfs_info3_param *refs = NULL; | |
1386 | int numrefs = 0; | |
5072010c | 1387 | struct cifs_ses *root_ses = NULL, *ses; |
54be1f6c PA |
1388 | |
1389 | xid = get_xid(); | |
1390 | ||
1391 | path = tcon->dfs_path + 1; | |
1392 | ||
1393 | rc = get_normalized_path(path, &npath); | |
1394 | if (rc) | |
1395 | goto out; | |
1396 | ||
1397 | mutex_lock(&dfs_cache_list_lock); | |
1398 | ce = find_cache_entry(npath, &h); | |
1399 | mutex_unlock(&dfs_cache_list_lock); | |
1400 | ||
1401 | if (IS_ERR(ce)) { | |
1402 | rc = PTR_ERR(ce); | |
1403 | goto out; | |
1404 | } | |
1405 | ||
1406 | if (!cache_entry_expired(ce)) | |
1407 | goto out; | |
1408 | ||
5072010c PAS |
1409 | /* If it's a DFS Link, then use root SMB session for refreshing it */ |
1410 | if (is_dfs_link(npath)) { | |
1411 | ses = root_ses = find_root_ses(vi, tcon, npath); | |
1412 | if (IS_ERR(ses)) { | |
1413 | rc = PTR_ERR(ses); | |
1414 | root_ses = NULL; | |
1415 | goto out; | |
1416 | } | |
1417 | } else { | |
1418 | ses = tcon->ses; | |
1419 | } | |
1420 | ||
1421 | if (unlikely(!ses->server->ops->get_dfs_refer)) { | |
54be1f6c PA |
1422 | rc = -EOPNOTSUPP; |
1423 | } else { | |
5072010c PAS |
1424 | rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs, |
1425 | &numrefs, dc->dc_nlsc, | |
1426 | tcon->remap); | |
54be1f6c PA |
1427 | if (!rc) { |
1428 | mutex_lock(&dfs_cache_list_lock); | |
1429 | ce = __update_cache_entry(npath, refs, numrefs); | |
1430 | mutex_unlock(&dfs_cache_list_lock); | |
1431 | dump_refs(refs, numrefs); | |
1432 | free_dfs_info_array(refs, numrefs); | |
1433 | if (IS_ERR(ce)) | |
1434 | rc = PTR_ERR(ce); | |
1435 | } | |
1436 | } | |
5072010c | 1437 | |
54be1f6c | 1438 | out: |
5072010c PAS |
1439 | if (root_ses) |
1440 | cifs_put_smb_ses(root_ses); | |
1441 | ||
54be1f6c PA |
1442 | free_xid(xid); |
1443 | free_normalized_path(path, npath); | |
1444 | } | |
1445 | ||
1446 | /* | |
1447 | * Worker that will refresh DFS cache based on lowest TTL value from a DFS | |
1448 | * referral. | |
54be1f6c PA |
1449 | */ |
1450 | static void refresh_cache_worker(struct work_struct *work) | |
1451 | { | |
1452 | struct dfs_cache *dc = container_of(work, struct dfs_cache, | |
1453 | dc_refresh.work); | |
1454 | struct dfs_cache_vol_info *vi; | |
1455 | struct TCP_Server_Info *server; | |
1456 | LIST_HEAD(list); | |
1457 | struct cifs_tcon *tcon, *ntcon; | |
1458 | ||
1459 | mutex_lock(&dc->dc_lock); | |
1460 | ||
1461 | list_for_each_entry(vi, &dc->dc_vol_list, vi_list) { | |
1462 | server = cifs_find_tcp_session(&vi->vi_vol); | |
1463 | if (IS_ERR_OR_NULL(server)) | |
1464 | continue; | |
1465 | if (server->tcpStatus != CifsGood) | |
1466 | goto next; | |
1467 | get_tcons(server, &list); | |
1468 | list_for_each_entry_safe(tcon, ntcon, &list, ulist) { | |
5072010c | 1469 | do_refresh_tcon(dc, vi, tcon); |
54be1f6c PA |
1470 | list_del_init(&tcon->ulist); |
1471 | cifs_put_tcon(tcon); | |
1472 | } | |
1473 | next: | |
1474 | cifs_put_tcp_session(server, 0); | |
1475 | } | |
1476 | queue_delayed_work(cifsiod_wq, &dc->dc_refresh, dc->dc_ttl * HZ); | |
1477 | mutex_unlock(&dc->dc_lock); | |
1478 | } |