1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/fs/lockd/svcsubs.c |
4 | * |
5 | * Various support routines for the NLM server. |
6 | * |
7 | * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> |
8 | */ |
9 | |
10 | #include <linux/types.h> |
11 | #include <linux/string.h> |
12 | #include <linux/time.h> |
13 | #include <linux/in.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/sunrpc/svc.h> |
17 | #include <linux/sunrpc/addr.h> |
18 | #include <linux/lockd/lockd.h> |
19 | #include <linux/lockd/share.h> |
20 | #include <linux/module.h> |
21 | #include <linux/mount.h> |
22 | #include <uapi/linux/nfs2.h> |
23 | |
24 | #define NLMDBG_FACILITY NLMDBG_SVCSUBS |
25 | |
26 | |
27 | /* |
28 | * Global file hash table |
29 | */ |
30 | #define FILE_HASH_BITS 7 |
31 | #define FILE_NRHASH (1<<FILE_HASH_BITS) |
32 | static struct hlist_head nlm_files[FILE_NRHASH]; |
33 | static DEFINE_MUTEX(nlm_file_mutex); |
34 | |
35 | #ifdef CONFIG_SUNRPC_DEBUG |
36 | static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f) |
37 | { |
38 | u32 *fhp = (u32*)f->data; |
39 | |
40 | /* print the first 32 bytes of the fh */ |
41 | dprintk("lockd: %s (%08x %08x %08x %08x %08x %08x %08x %08x)\n" , |
42 | msg, fhp[0], fhp[1], fhp[2], fhp[3], |
43 | fhp[4], fhp[5], fhp[6], fhp[7]); |
44 | } |
45 | |
46 | static inline void nlm_debug_print_file(char *msg, struct nlm_file *file) |
47 | { |
48 | struct inode *inode = nlmsvc_file_inode(file); |
49 | |
50 | dprintk("lockd: %s %s/%ld\n" , |
51 | msg, inode->i_sb->s_id, inode->i_ino); |
52 | } |
53 | #else |
54 | static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f) |
55 | { |
56 | return; |
57 | } |
58 | |
59 | static inline void nlm_debug_print_file(char *msg, struct nlm_file *file) |
60 | { |
61 | return; |
62 | } |
63 | #endif |
64 | |
65 | static inline unsigned int file_hash(struct nfs_fh *f) |
66 | { |
67 | unsigned int tmp=0; |
68 | int i; |
69 | for (i=0; i<NFS2_FHSIZE;i++) |
70 | tmp += f->data[i]; |
71 | return tmp & (FILE_NRHASH - 1); |
72 | } |
73 | |
74 | int lock_to_openmode(struct file_lock *lock) |
75 | { |
76 | return (lock->fl_type == F_WRLCK) ? O_WRONLY : O_RDONLY; |
77 | } |
78 | |
79 | /* |
80 | * Open the file. Note that if we're reexporting, for example, |
81 | * this could block the lockd thread for a while. |
82 | * |
83 | * We have to make sure we have the right credential to open |
84 | * the file. |
85 | */ |
86 | static __be32 nlm_do_fopen(struct svc_rqst *rqstp, |
87 | struct nlm_file *file, int mode) |
88 | { |
89 | struct file **fp = &file->f_file[mode]; |
90 | __be32 nfserr; |
91 | |
92 | if (*fp) |
93 | return 0; |
94 | nfserr = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, mode); |
95 | if (nfserr) |
96 | dprintk("lockd: open failed (error %d)\n" , nfserr); |
97 | return nfserr; |
98 | } |
99 | |
100 | /* |
101 | * Lookup file info. If it doesn't exist, create a file info struct |
102 | * and open a (VFS) file for the given inode. |
103 | */ |
104 | __be32 |
105 | nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, |
106 | struct nlm_lock *lock) |
107 | { |
108 | struct nlm_file *file; |
109 | unsigned int hash; |
110 | __be32 nfserr; |
111 | int mode; |
112 | |
113 | nlm_debug_print_fh(msg: "nlm_lookup_file" , f: &lock->fh); |
114 | |
115 | hash = file_hash(f: &lock->fh); |
116 | mode = lock_to_openmode(lock: &lock->fl); |
117 | |
118 | /* Lock file table */ |
119 | mutex_lock(&nlm_file_mutex); |
120 | |
121 | hlist_for_each_entry(file, &nlm_files[hash], f_list) |
122 | if (!nfs_compare_fh(a: &file->f_handle, b: &lock->fh)) { |
123 | mutex_lock(&file->f_mutex); |
124 | nfserr = nlm_do_fopen(rqstp, file, mode); |
125 | mutex_unlock(lock: &file->f_mutex); |
126 | goto found; |
127 | } |
128 | nlm_debug_print_fh(msg: "creating file for" , f: &lock->fh); |
129 | |
130 | nfserr = nlm_lck_denied_nolocks; |
131 | file = kzalloc(size: sizeof(*file), GFP_KERNEL); |
132 | if (!file) |
133 | goto out_free; |
134 | |
135 | memcpy(&file->f_handle, &lock->fh, sizeof(struct nfs_fh)); |
136 | mutex_init(&file->f_mutex); |
137 | INIT_HLIST_NODE(h: &file->f_list); |
138 | INIT_LIST_HEAD(list: &file->f_blocks); |
139 | |
140 | nfserr = nlm_do_fopen(rqstp, file, mode); |
141 | if (nfserr) |
142 | goto out_unlock; |
143 | |
144 | hlist_add_head(n: &file->f_list, h: &nlm_files[hash]); |
145 | |
146 | found: |
147 | dprintk("lockd: found file %p (count %d)\n" , file, file->f_count); |
148 | *result = file; |
149 | file->f_count++; |
150 | |
151 | out_unlock: |
152 | mutex_unlock(lock: &nlm_file_mutex); |
153 | return nfserr; |
154 | |
155 | out_free: |
156 | kfree(objp: file); |
157 | goto out_unlock; |
158 | } |
159 | |
160 | /* |
161 | * Delete a file after having released all locks, blocks and shares |
162 | */ |
163 | static inline void |
164 | nlm_delete_file(struct nlm_file *file) |
165 | { |
166 | nlm_debug_print_file(msg: "closing file" , file); |
167 | if (!hlist_unhashed(h: &file->f_list)) { |
168 | hlist_del(n: &file->f_list); |
169 | if (file->f_file[O_RDONLY]) |
170 | nlmsvc_ops->fclose(file->f_file[O_RDONLY]); |
171 | if (file->f_file[O_WRONLY]) |
172 | nlmsvc_ops->fclose(file->f_file[O_WRONLY]); |
173 | kfree(objp: file); |
174 | } else { |
175 | printk(KERN_WARNING "lockd: attempt to release unknown file!\n" ); |
176 | } |
177 | } |
178 | |
179 | static int nlm_unlock_files(struct nlm_file *file, const struct file_lock *fl) |
180 | { |
181 | struct file_lock lock; |
182 | |
183 | locks_init_lock(&lock); |
184 | lock.fl_type = F_UNLCK; |
185 | lock.fl_start = 0; |
186 | lock.fl_end = OFFSET_MAX; |
187 | lock.fl_owner = fl->fl_owner; |
188 | lock.fl_pid = fl->fl_pid; |
189 | lock.fl_flags = FL_POSIX; |
190 | |
191 | lock.fl_file = file->f_file[O_RDONLY]; |
192 | if (lock.fl_file && vfs_lock_file(lock.fl_file, F_SETLK, &lock, NULL)) |
193 | goto out_err; |
194 | lock.fl_file = file->f_file[O_WRONLY]; |
195 | if (lock.fl_file && vfs_lock_file(lock.fl_file, F_SETLK, &lock, NULL)) |
196 | goto out_err; |
197 | return 0; |
198 | out_err: |
199 | pr_warn("lockd: unlock failure in %s:%d\n" , __FILE__, __LINE__); |
200 | return 1; |
201 | } |
202 | |
203 | /* |
204 | * Loop over all locks on the given file and perform the specified |
205 | * action. |
206 | */ |
207 | static int |
208 | nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file, |
209 | nlm_host_match_fn_t match) |
210 | { |
211 | struct inode *inode = nlmsvc_file_inode(file); |
212 | struct file_lock *fl; |
213 | struct file_lock_context *flctx = locks_inode_context(inode); |
214 | struct nlm_host *lockhost; |
215 | |
216 | if (!flctx || list_empty_careful(head: &flctx->flc_posix)) |
217 | return 0; |
218 | again: |
219 | file->f_locks = 0; |
220 | spin_lock(lock: &flctx->flc_lock); |
221 | list_for_each_entry(fl, &flctx->flc_posix, fl_list) { |
222 | if (fl->fl_lmops != &nlmsvc_lock_operations) |
223 | continue; |
224 | |
225 | /* update current lock count */ |
226 | file->f_locks++; |
227 | |
228 | lockhost = ((struct nlm_lockowner *)fl->fl_owner)->host; |
229 | if (match(lockhost, host)) { |
230 | |
231 | spin_unlock(lock: &flctx->flc_lock); |
232 | if (nlm_unlock_files(file, fl)) |
233 | return 1; |
234 | goto again; |
235 | } |
236 | } |
237 | spin_unlock(lock: &flctx->flc_lock); |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static int |
243 | nlmsvc_always_match(void *dummy1, struct nlm_host *dummy2) |
244 | { |
245 | return 1; |
246 | } |
247 | |
248 | /* |
249 | * Inspect a single file |
250 | */ |
251 | static inline int |
252 | nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, nlm_host_match_fn_t match) |
253 | { |
254 | nlmsvc_traverse_blocks(host, file, match); |
255 | nlmsvc_traverse_shares(host, file, match); |
256 | return nlm_traverse_locks(host, file, match); |
257 | } |
258 | |
259 | /* |
260 | * Quick check whether there are still any locks, blocks or |
261 | * shares on a given file. |
262 | */ |
263 | static inline int |
264 | nlm_file_inuse(struct nlm_file *file) |
265 | { |
266 | struct inode *inode = nlmsvc_file_inode(file); |
267 | struct file_lock *fl; |
268 | struct file_lock_context *flctx = locks_inode_context(inode); |
269 | |
270 | if (file->f_count || !list_empty(head: &file->f_blocks) || file->f_shares) |
271 | return 1; |
272 | |
273 | if (flctx && !list_empty_careful(head: &flctx->flc_posix)) { |
274 | spin_lock(lock: &flctx->flc_lock); |
275 | list_for_each_entry(fl, &flctx->flc_posix, fl_list) { |
276 | if (fl->fl_lmops == &nlmsvc_lock_operations) { |
277 | spin_unlock(lock: &flctx->flc_lock); |
278 | return 1; |
279 | } |
280 | } |
281 | spin_unlock(lock: &flctx->flc_lock); |
282 | } |
283 | file->f_locks = 0; |
284 | return 0; |
285 | } |
286 | |
287 | static void nlm_close_files(struct nlm_file *file) |
288 | { |
289 | if (file->f_file[O_RDONLY]) |
290 | nlmsvc_ops->fclose(file->f_file[O_RDONLY]); |
291 | if (file->f_file[O_WRONLY]) |
292 | nlmsvc_ops->fclose(file->f_file[O_WRONLY]); |
293 | } |
294 | |
295 | /* |
296 | * Loop over all files in the file table. |
297 | */ |
298 | static int |
299 | nlm_traverse_files(void *data, nlm_host_match_fn_t match, |
300 | int (*is_failover_file)(void *data, struct nlm_file *file)) |
301 | { |
302 | struct hlist_node *next; |
303 | struct nlm_file *file; |
304 | int i, ret = 0; |
305 | |
306 | mutex_lock(&nlm_file_mutex); |
307 | for (i = 0; i < FILE_NRHASH; i++) { |
308 | hlist_for_each_entry_safe(file, next, &nlm_files[i], f_list) { |
309 | if (is_failover_file && !is_failover_file(data, file)) |
310 | continue; |
311 | file->f_count++; |
312 | mutex_unlock(lock: &nlm_file_mutex); |
313 | |
314 | /* Traverse locks, blocks and shares of this file |
315 | * and update file->f_locks count */ |
316 | if (nlm_inspect_file(host: data, file, match)) |
317 | ret = 1; |
318 | |
319 | mutex_lock(&nlm_file_mutex); |
320 | file->f_count--; |
321 | /* No more references to this file. Let go of it. */ |
322 | if (list_empty(head: &file->f_blocks) && !file->f_locks |
323 | && !file->f_shares && !file->f_count) { |
324 | hlist_del(n: &file->f_list); |
325 | nlm_close_files(file); |
326 | kfree(objp: file); |
327 | } |
328 | } |
329 | } |
330 | mutex_unlock(lock: &nlm_file_mutex); |
331 | return ret; |
332 | } |
333 | |
334 | /* |
335 | * Release file. If there are no more remote locks on this file, |
336 | * close it and free the handle. |
337 | * |
338 | * Note that we can't do proper reference counting without major |
339 | * contortions because the code in fs/locks.c creates, deletes and |
340 | * splits locks without notification. Our only way is to walk the |
341 | * entire lock list each time we remove a lock. |
342 | */ |
343 | void |
344 | nlm_release_file(struct nlm_file *file) |
345 | { |
346 | dprintk("lockd: nlm_release_file(%p, ct = %d)\n" , |
347 | file, file->f_count); |
348 | |
349 | /* Lock file table */ |
350 | mutex_lock(&nlm_file_mutex); |
351 | |
352 | /* If there are no more locks etc, delete the file */ |
353 | if (--file->f_count == 0 && !nlm_file_inuse(file)) |
354 | nlm_delete_file(file); |
355 | |
356 | mutex_unlock(lock: &nlm_file_mutex); |
357 | } |
358 | |
359 | /* |
360 | * Helpers function for resource traversal |
361 | * |
362 | * nlmsvc_mark_host: |
363 | * used by the garbage collector; simply sets h_inuse only for those |
364 | * hosts, which passed network check. |
365 | * Always returns 0. |
366 | * |
367 | * nlmsvc_same_host: |
368 | * returns 1 iff the two hosts match. Used to release |
369 | * all resources bound to a specific host. |
370 | * |
371 | * nlmsvc_is_client: |
372 | * returns 1 iff the host is a client. |
373 | * Used by nlmsvc_invalidate_all |
374 | */ |
375 | |
376 | static int |
377 | nlmsvc_mark_host(void *data, struct nlm_host *hint) |
378 | { |
379 | struct nlm_host *host = data; |
380 | |
381 | if ((hint->net == NULL) || |
382 | (host->net == hint->net)) |
383 | host->h_inuse = 1; |
384 | return 0; |
385 | } |
386 | |
387 | static int |
388 | nlmsvc_same_host(void *data, struct nlm_host *other) |
389 | { |
390 | struct nlm_host *host = data; |
391 | |
392 | return host == other; |
393 | } |
394 | |
395 | static int |
396 | nlmsvc_is_client(void *data, struct nlm_host *dummy) |
397 | { |
398 | struct nlm_host *host = data; |
399 | |
400 | if (host->h_server) { |
401 | /* we are destroying locks even though the client |
402 | * hasn't asked us too, so don't unmonitor the |
403 | * client |
404 | */ |
405 | if (host->h_nsmhandle) |
406 | host->h_nsmhandle->sm_sticky = 1; |
407 | return 1; |
408 | } else |
409 | return 0; |
410 | } |
411 | |
412 | /* |
413 | * Mark all hosts that still hold resources |
414 | */ |
415 | void |
416 | nlmsvc_mark_resources(struct net *net) |
417 | { |
418 | struct nlm_host hint; |
419 | |
420 | dprintk("lockd: %s for net %x\n" , __func__, net ? net->ns.inum : 0); |
421 | hint.net = net; |
422 | nlm_traverse_files(data: &hint, match: nlmsvc_mark_host, NULL); |
423 | } |
424 | |
425 | /* |
426 | * Release all resources held by the given client |
427 | */ |
428 | void |
429 | nlmsvc_free_host_resources(struct nlm_host *host) |
430 | { |
431 | dprintk("lockd: nlmsvc_free_host_resources\n" ); |
432 | |
433 | if (nlm_traverse_files(data: host, match: nlmsvc_same_host, NULL)) { |
434 | printk(KERN_WARNING |
435 | "lockd: couldn't remove all locks held by %s\n" , |
436 | host->h_name); |
437 | BUG(); |
438 | } |
439 | } |
440 | |
441 | /** |
442 | * nlmsvc_invalidate_all - remove all locks held for clients |
443 | * |
444 | * Release all locks held by NFS clients. |
445 | * |
446 | */ |
447 | void |
448 | nlmsvc_invalidate_all(void) |
449 | { |
450 | /* |
451 | * Previously, the code would call |
452 | * nlmsvc_free_host_resources for each client in |
453 | * turn, which is about as inefficient as it gets. |
454 | * Now we just do it once in nlm_traverse_files. |
455 | */ |
456 | nlm_traverse_files(NULL, match: nlmsvc_is_client, NULL); |
457 | } |
458 | |
459 | |
460 | static int |
461 | nlmsvc_match_sb(void *datap, struct nlm_file *file) |
462 | { |
463 | struct super_block *sb = datap; |
464 | |
465 | return sb == nlmsvc_file_inode(file)->i_sb; |
466 | } |
467 | |
468 | /** |
469 | * nlmsvc_unlock_all_by_sb - release locks held on this file system |
470 | * @sb: super block |
471 | * |
472 | * Release all locks held by clients accessing this file system. |
473 | */ |
474 | int |
475 | nlmsvc_unlock_all_by_sb(struct super_block *sb) |
476 | { |
477 | int ret; |
478 | |
479 | ret = nlm_traverse_files(data: sb, match: nlmsvc_always_match, is_failover_file: nlmsvc_match_sb); |
480 | return ret ? -EIO : 0; |
481 | } |
482 | EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_sb); |
483 | |
484 | static int |
485 | nlmsvc_match_ip(void *datap, struct nlm_host *host) |
486 | { |
487 | return rpc_cmp_addr(sap1: nlm_srcaddr(host), sap2: datap); |
488 | } |
489 | |
490 | /** |
491 | * nlmsvc_unlock_all_by_ip - release local locks by IP address |
492 | * @server_addr: server's IP address as seen by clients |
493 | * |
494 | * Release all locks held by clients accessing this host |
495 | * via the passed in IP address. |
496 | */ |
497 | int |
498 | nlmsvc_unlock_all_by_ip(struct sockaddr *server_addr) |
499 | { |
500 | int ret; |
501 | |
502 | ret = nlm_traverse_files(data: server_addr, match: nlmsvc_match_ip, NULL); |
503 | return ret ? -EIO : 0; |
504 | } |
505 | EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_ip); |
506 | |