| 1 | |
| 2 | |
| 3 | * linux/fs/lockd/svcsubs.c
|
| 4 | *
|
| 5 | * Various support routines for the NLM server.
|
| 6 | *
|
| 7 | * Copyright (C) 1996, Olaf Kirch <[email protected]>
|
| 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/module.h>
|
| 19 | #include <linux/mount.h>
|
| 20 |
|
| 21 | #include "lockd.h"
|
| 22 | #include "share.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 |
|
| 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/%llu\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 < LOCKD_FH_HASH_SIZE; 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_is_write(lock) ? 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 | * @mode is O_RDONLY, O_WRONLY, or O_RDWR. O_RDWR means success
|
| 87 | * is achieved with EITHER O_RDONLY or O_WRONLY; it does not
|
| 88 | * require both.
|
| 89 |
|
| 90 | static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
|
| 91 | struct nlm_file *file, int mode)
|
| 92 | {
|
| 93 | const struct nlmsvc_binding *ops;
|
| 94 | __be32 nlmerr = nlm__int__failed;
|
| 95 | __be32 deferred = 0;
|
| 96 | int error;
|
| 97 | int m;
|
| 98 |
|
| 99 | rcu_read_lock();
|
| 100 | ops = rcu_dereference(nlmsvc_ops);
|
| 101 | if (!ops || !try_module_get(ops->owner)) {
|
| 102 | rcu_read_unlock();
|
| 103 | return nlm__int__failed;
|
| 104 | }
|
| 105 | rcu_read_unlock();
|
| 106 |
|
| 107 | for (m = O_RDONLY; m <= O_WRONLY; m++) {
|
| 108 | struct file **fp = &file->f_file[m];
|
| 109 |
|
| 110 | if (mode != O_RDWR && mode != m)
|
| 111 | continue;
|
| 112 | if (*fp) {
|
| 113 | module_put(ops->owner);
|
| 114 | return nlm_granted;
|
| 115 | }
|
| 116 |
|
| 117 | error = ops->fopen(rqstp, &file->f_handle, fp, m);
|
| 118 | if (!error) {
|
| 119 | module_put(ops->owner);
|
| 120 | return nlm_granted;
|
| 121 | }
|
| 122 |
|
| 123 | dprintk("lockd: open failed (errno %d)\n", error);
|
| 124 | switch (error) {
|
| 125 | case -EWOULDBLOCK:
|
| 126 | nlmerr = nlm__int__drop_reply;
|
| 127 | deferred = nlmerr;
|
| 128 | break;
|
| 129 | case -ESTALE:
|
| 130 | nlmerr = nlm__int__stale_fh;
|
| 131 | break;
|
| 132 | default:
|
| 133 | nlmerr = nlm__int__failed;
|
| 134 | break;
|
| 135 | }
|
| 136 | }
|
| 137 |
|
| 138 | module_put(ops->owner);
|
| 139 | return deferred ? deferred : nlmerr;
|
| 140 | }
|
| 141 |
|
| 142 | |
| 143 | * Lookup file info. If it doesn't exist, create a file info struct
|
| 144 | * and open a (VFS) file for the given inode.
|
| 145 |
|
| 146 | __be32
|
| 147 | nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result,
|
| 148 | struct lockd_lock *lock, int mode)
|
| 149 | {
|
| 150 | struct nlm_file *file;
|
| 151 | unsigned int hash;
|
| 152 | __be32 nfserr;
|
| 153 |
|
| 154 | nlm_debug_print_fh("nlm_lookup_file", &lock->fh);
|
| 155 |
|
| 156 | hash = file_hash(&lock->fh);
|
| 157 |
|
| 158 |
|
| 159 | mutex_lock(&nlm_file_mutex);
|
| 160 |
|
| 161 | hlist_for_each_entry(file, &nlm_files[hash], f_list)
|
| 162 | if (!nfs_compare_fh(&file->f_handle, &lock->fh)) {
|
| 163 | mutex_lock(&file->f_mutex);
|
| 164 | nfserr = nlm_do_fopen(rqstp, file, mode);
|
| 165 | mutex_unlock(&file->f_mutex);
|
| 166 | if (nfserr)
|
| 167 | goto out_unlock;
|
| 168 | goto found;
|
| 169 | }
|
| 170 | nlm_debug_print_fh("creating file for", &lock->fh);
|
| 171 |
|
| 172 | nfserr = nlm_lck_denied_nolocks;
|
| 173 | file = kzalloc_obj(*file);
|
| 174 | if (!file)
|
| 175 | goto out_free;
|
| 176 |
|
| 177 | memcpy(&file->f_handle, &lock->fh, sizeof(struct nfs_fh));
|
| 178 | mutex_init(&file->f_mutex);
|
| 179 | INIT_HLIST_NODE(&file->f_list);
|
| 180 | INIT_LIST_HEAD(&file->f_blocks);
|
| 181 |
|
| 182 | nfserr = nlm_do_fopen(rqstp, file, mode);
|
| 183 | if (nfserr)
|
| 184 | goto out_free;
|
| 185 |
|
| 186 | hlist_add_head(&file->f_list, &nlm_files[hash]);
|
| 187 |
|
| 188 | found:
|
| 189 | dprintk("lockd: found file %p (count %d)\n", file, file->f_count);
|
| 190 | *result = file;
|
| 191 | file->f_count++;
|
| 192 |
|
| 193 | out_unlock:
|
| 194 | mutex_unlock(&nlm_file_mutex);
|
| 195 | return nfserr;
|
| 196 |
|
| 197 | out_free:
|
| 198 | kfree(file);
|
| 199 | goto out_unlock;
|
| 200 | }
|
| 201 |
|
| 202 | |
| 203 | * Release the struct file references held by a nlm_file.
|
| 204 |
|
| 205 | static void nlm_release_files(struct nlm_file *file)
|
| 206 | {
|
| 207 | const struct nlmsvc_binding *ops;
|
| 208 | bool have_ops;
|
| 209 |
|
| 210 | rcu_read_lock();
|
| 211 | ops = rcu_dereference(nlmsvc_ops);
|
| 212 | have_ops = ops && try_module_get(ops->owner);
|
| 213 | rcu_read_unlock();
|
| 214 |
|
| 215 | if (have_ops) {
|
| 216 | if (file->f_file[O_RDONLY])
|
| 217 | ops->fclose(file->f_file[O_RDONLY]);
|
| 218 | if (file->f_file[O_WRONLY])
|
| 219 | ops->fclose(file->f_file[O_WRONLY]);
|
| 220 | module_put(ops->owner);
|
| 221 | } else {
|
| 222 | if (file->f_file[O_RDONLY])
|
| 223 | fput(file->f_file[O_RDONLY]);
|
| 224 | if (file->f_file[O_WRONLY])
|
| 225 | fput(file->f_file[O_WRONLY]);
|
| 226 | }
|
| 227 | }
|
| 228 |
|
| 229 | |
| 230 | * Delete a file after having released all locks, blocks and shares
|
| 231 |
|
| 232 | static inline void
|
| 233 | nlm_delete_file(struct nlm_file *file)
|
| 234 | {
|
| 235 | nlm_debug_print_file("closing file", file);
|
| 236 | if (!hlist_unhashed(&file->f_list)) {
|
| 237 | hlist_del(&file->f_list);
|
| 238 | nlm_release_files(file);
|
| 239 | kfree(file);
|
| 240 | } else {
|
| 241 | printk(KERN_WARNING "lockd: attempt to release unknown file!\n");
|
| 242 | }
|
| 243 | }
|
| 244 |
|
| 245 | static int nlm_unlock_files(struct nlm_file *file, const struct file_lock *fl)
|
| 246 | {
|
| 247 | struct file_lock lock;
|
| 248 |
|
| 249 | locks_init_lock(&lock);
|
| 250 | lock.c.flc_type = F_UNLCK;
|
| 251 | lock.fl_start = 0;
|
| 252 | lock.fl_end = OFFSET_MAX;
|
| 253 | lock.c.flc_owner = fl->c.flc_owner;
|
| 254 | lock.c.flc_pid = fl->c.flc_pid;
|
| 255 | lock.c.flc_flags = FL_POSIX;
|
| 256 |
|
| 257 | lock.c.flc_file = file->f_file[O_RDONLY];
|
| 258 | if (lock.c.flc_file && vfs_lock_file(lock.c.flc_file, F_SETLK, &lock, NULL))
|
| 259 | goto out_err;
|
| 260 | lock.c.flc_file = file->f_file[O_WRONLY];
|
| 261 | if (lock.c.flc_file && vfs_lock_file(lock.c.flc_file, F_SETLK, &lock, NULL))
|
| 262 | goto out_err;
|
| 263 | return 0;
|
| 264 | out_err:
|
| 265 | pr_warn("lockd: unlock failure in %s:%d\n", __FILE__, __LINE__);
|
| 266 | return 1;
|
| 267 | }
|
| 268 |
|
| 269 | |
| 270 | * Loop over all locks on the given file and perform the specified
|
| 271 | * action.
|
| 272 |
|
| 273 | static int
|
| 274 | nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file,
|
| 275 | nlm_host_match_fn_t match)
|
| 276 | {
|
| 277 | struct inode *inode = nlmsvc_file_inode(file);
|
| 278 | struct file_lock *fl;
|
| 279 | struct file_lock_context *flctx = locks_inode_context(inode);
|
| 280 | struct nlm_host *lockhost;
|
| 281 |
|
| 282 | if (!flctx || list_empty_careful(&flctx->flc_posix))
|
| 283 | return 0;
|
| 284 | again:
|
| 285 | file->f_locks = 0;
|
| 286 | spin_lock(&flctx->flc_lock);
|
| 287 | for_each_file_lock(fl, &flctx->flc_posix) {
|
| 288 | if (fl->fl_lmops != &nlmsvc_lock_operations)
|
| 289 | continue;
|
| 290 |
|
| 291 |
|
| 292 | file->f_locks++;
|
| 293 |
|
| 294 | lockhost = ((struct nlm_lockowner *) fl->c.flc_owner)->host;
|
| 295 | if (match(lockhost, host)) {
|
| 296 |
|
| 297 | spin_unlock(&flctx->flc_lock);
|
| 298 | if (nlm_unlock_files(file, fl))
|
| 299 | return 1;
|
| 300 | goto again;
|
| 301 | }
|
| 302 | }
|
| 303 | spin_unlock(&flctx->flc_lock);
|
| 304 |
|
| 305 | return 0;
|
| 306 | }
|
| 307 |
|
| 308 | static int
|
| 309 | nlmsvc_always_match(void *dummy1, struct nlm_host *dummy2)
|
| 310 | {
|
| 311 | return 1;
|
| 312 | }
|
| 313 |
|
| 314 | |
| 315 | * Inspect a single file
|
| 316 |
|
| 317 | static inline int
|
| 318 | nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, nlm_host_match_fn_t match)
|
| 319 | {
|
| 320 | nlmsvc_traverse_blocks(host, file, match);
|
| 321 | nlmsvc_traverse_shares(host, file, match);
|
| 322 | return nlm_traverse_locks(host, file, match);
|
| 323 | }
|
| 324 |
|
| 325 | |
| 326 | * Quick check whether there are still any locks, blocks or
|
| 327 | * shares on a given file.
|
| 328 |
|
| 329 | static inline int
|
| 330 | nlm_file_inuse(struct nlm_file *file)
|
| 331 | {
|
| 332 | struct inode *inode = nlmsvc_file_inode(file);
|
| 333 | struct file_lock *fl;
|
| 334 | struct file_lock_context *flctx = locks_inode_context(inode);
|
| 335 |
|
| 336 | if (file->f_count || !list_empty(&file->f_blocks) || file->f_shares)
|
| 337 | return 1;
|
| 338 |
|
| 339 | if (flctx && !list_empty_careful(&flctx->flc_posix)) {
|
| 340 | spin_lock(&flctx->flc_lock);
|
| 341 | for_each_file_lock(fl, &flctx->flc_posix) {
|
| 342 | if (fl->fl_lmops == &nlmsvc_lock_operations) {
|
| 343 | spin_unlock(&flctx->flc_lock);
|
| 344 | return 1;
|
| 345 | }
|
| 346 | }
|
| 347 | spin_unlock(&flctx->flc_lock);
|
| 348 | }
|
| 349 | file->f_locks = 0;
|
| 350 | return 0;
|
| 351 | }
|
| 352 |
|
| 353 | static void nlm_file_release(struct nlm_file *file)
|
| 354 | {
|
| 355 | if (!nlm_file_inuse(file))
|
| 356 | nlm_delete_file(file);
|
| 357 | }
|
| 358 |
|
| 359 | |
| 360 | * Loop over all files in the file table.
|
| 361 |
|
| 362 | static int
|
| 363 | nlm_traverse_files(void *data, nlm_host_match_fn_t match,
|
| 364 | int (*is_failover_file)(void *data, struct nlm_file *file))
|
| 365 | {
|
| 366 | struct nlm_file *file, *next;
|
| 367 | int i, ret = 0;
|
| 368 |
|
| 369 | mutex_lock(&nlm_file_mutex);
|
| 370 | for (i = 0; i < FILE_NRHASH; i++) {
|
| 371 | file = hlist_entry_safe(nlm_files[i].first,
|
| 372 | struct nlm_file, f_list);
|
| 373 | if (file)
|
| 374 | file->f_count++;
|
| 375 | while (file) {
|
| 376 | |
| 377 | * Pin the next neighbour before we drop the mutex
|
| 378 | * for nlm_inspect_file(); a concurrent
|
| 379 | * nlm_release_file() under the same mutex would
|
| 380 | * otherwise be free to unlink and kfree it during
|
| 381 | * the unlock window, leaving us to dereference a
|
| 382 | * freed slab when we walked to next afterwards.
|
| 383 |
|
| 384 | next = hlist_entry_safe(file->f_list.next,
|
| 385 | struct nlm_file, f_list);
|
| 386 | if (next)
|
| 387 | next->f_count++;
|
| 388 |
|
| 389 | if (!is_failover_file || is_failover_file(data, file)) {
|
| 390 | mutex_unlock(&nlm_file_mutex);
|
| 391 |
|
| 392 | if (nlm_inspect_file(data, file, match))
|
| 393 | ret = 1;
|
| 394 |
|
| 395 | mutex_lock(&nlm_file_mutex);
|
| 396 | }
|
| 397 |
|
| 398 | file->f_count--;
|
| 399 | nlm_file_release(file);
|
| 400 | file = next;
|
| 401 | }
|
| 402 | }
|
| 403 | mutex_unlock(&nlm_file_mutex);
|
| 404 | return ret;
|
| 405 | }
|
| 406 |
|
| 407 | |
| 408 | * Release file. If there are no more remote locks on this file,
|
| 409 | * close it and free the handle.
|
| 410 | *
|
| 411 | * Note that we can't do proper reference counting without major
|
| 412 | * contortions because the code in fs/locks.c creates, deletes and
|
| 413 | * splits locks without notification. Our only way is to walk the
|
| 414 | * entire lock list each time we remove a lock.
|
| 415 |
|
| 416 | void
|
| 417 | nlm_release_file(struct nlm_file *file)
|
| 418 | {
|
| 419 | dprintk("lockd: nlm_release_file(%p, ct = %d)\n",
|
| 420 | file, file->f_count);
|
| 421 |
|
| 422 |
|
| 423 | mutex_lock(&nlm_file_mutex);
|
| 424 |
|
| 425 |
|
| 426 | if (--file->f_count == 0 && !nlm_file_inuse(file))
|
| 427 | nlm_delete_file(file);
|
| 428 |
|
| 429 | mutex_unlock(&nlm_file_mutex);
|
| 430 | }
|
| 431 |
|
| 432 | |
| 433 | * Helpers function for resource traversal
|
| 434 | *
|
| 435 | * nlmsvc_mark_host:
|
| 436 | * used by the garbage collector; simply sets h_inuse only for those
|
| 437 | * hosts, which passed network check.
|
| 438 | * Always returns 0.
|
| 439 | *
|
| 440 | * nlmsvc_same_host:
|
| 441 | * returns 1 iff the two hosts match. Used to release
|
| 442 | * all resources bound to a specific host.
|
| 443 | *
|
| 444 | * nlmsvc_is_client:
|
| 445 | * returns 1 iff the host is a client.
|
| 446 | * Used by nlmsvc_invalidate_all
|
| 447 |
|
| 448 |
|
| 449 | static int
|
| 450 | nlmsvc_mark_host(void *data, struct nlm_host *hint)
|
| 451 | {
|
| 452 | struct nlm_host *host = data;
|
| 453 |
|
| 454 | if ((hint->net == NULL) ||
|
| 455 | (host->net == hint->net))
|
| 456 | host->h_inuse = 1;
|
| 457 | return 0;
|
| 458 | }
|
| 459 |
|
| 460 | static int
|
| 461 | nlmsvc_same_host(void *data, struct nlm_host *other)
|
| 462 | {
|
| 463 | struct nlm_host *host = data;
|
| 464 |
|
| 465 | return host == other;
|
| 466 | }
|
| 467 |
|
| 468 | static int
|
| 469 | nlmsvc_is_client(void *data, struct nlm_host *dummy)
|
| 470 | {
|
| 471 | struct nlm_host *host = data;
|
| 472 |
|
| 473 | if (host->h_server) {
|
| 474 | |
| 475 | * hasn't asked us too, so don't unmonitor the
|
| 476 | * client
|
| 477 |
|
| 478 | if (host->h_nsmhandle)
|
| 479 | host->h_nsmhandle->sm_sticky = 1;
|
| 480 | return 1;
|
| 481 | } else
|
| 482 | return 0;
|
| 483 | }
|
| 484 |
|
| 485 | |
| 486 | * Mark all hosts that still hold resources
|
| 487 |
|
| 488 | void
|
| 489 | nlmsvc_mark_resources(struct net *net)
|
| 490 | {
|
| 491 | struct nlm_host hint;
|
| 492 |
|
| 493 | dprintk("lockd: %s for net %x\n", __func__, net ? net->ns.inum : 0);
|
| 494 | hint.net = net;
|
| 495 | nlm_traverse_files(&hint, nlmsvc_mark_host, NULL);
|
| 496 | }
|
| 497 |
|
| 498 | |
| 499 | * Release all resources held by the given client
|
| 500 |
|
| 501 | void
|
| 502 | nlmsvc_free_host_resources(struct nlm_host *host)
|
| 503 | {
|
| 504 | dprintk("lockd: nlmsvc_free_host_resources\n");
|
| 505 |
|
| 506 | if (nlm_traverse_files(host, nlmsvc_same_host, NULL)) {
|
| 507 | printk(KERN_WARNING
|
| 508 | "lockd: couldn't remove all locks held by %s\n",
|
| 509 | host->h_name);
|
| 510 | BUG();
|
| 511 | }
|
| 512 | }
|
| 513 |
|
| 514 | |
| 515 | * nlmsvc_invalidate_all - remove all locks held for clients
|
| 516 | *
|
| 517 | * Release all locks held by NFS clients.
|
| 518 | *
|
| 519 |
|
| 520 | void
|
| 521 | nlmsvc_invalidate_all(void)
|
| 522 | {
|
| 523 | |
| 524 | * Previously, the code would call
|
| 525 | * nlmsvc_free_host_resources for each client in
|
| 526 | * turn, which is about as inefficient as it gets.
|
| 527 | * Now we just do it once in nlm_traverse_files.
|
| 528 |
|
| 529 | nlm_traverse_files(NULL, nlmsvc_is_client, NULL);
|
| 530 | }
|
| 531 |
|
| 532 |
|
| 533 | static int
|
| 534 | nlmsvc_match_sb(void *datap, struct nlm_file *file)
|
| 535 | {
|
| 536 | struct super_block *sb = datap;
|
| 537 |
|
| 538 | return sb == nlmsvc_file_inode(file)->i_sb;
|
| 539 | }
|
| 540 |
|
| 541 | |
| 542 | * nlmsvc_unlock_all_by_sb - release locks held on this file system
|
| 543 | * @sb: super block
|
| 544 | *
|
| 545 | * Release all locks held by clients accessing this file system.
|
| 546 |
|
| 547 | int
|
| 548 | nlmsvc_unlock_all_by_sb(struct super_block *sb)
|
| 549 | {
|
| 550 | int ret;
|
| 551 |
|
| 552 | ret = nlm_traverse_files(sb, nlmsvc_always_match, nlmsvc_match_sb);
|
| 553 | return ret ? -EIO : 0;
|
| 554 | }
|
| 555 | EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_sb);
|
| 556 |
|
| 557 | static int
|
| 558 | nlmsvc_match_ip(void *datap, struct nlm_host *host)
|
| 559 | {
|
| 560 | return rpc_cmp_addr(nlm_srcaddr(datap), (struct sockaddr *)host);
|
| 561 | }
|
| 562 |
|
| 563 | |
| 564 | * nlmsvc_unlock_all_by_ip - release local locks by IP address
|
| 565 | * @server_addr: server's IP address as seen by clients
|
| 566 | *
|
| 567 | * Release all locks held by clients accessing this host
|
| 568 | * via the passed in IP address.
|
| 569 |
|
| 570 | int
|
| 571 | nlmsvc_unlock_all_by_ip(struct sockaddr *server_addr)
|
| 572 | {
|
| 573 | int ret;
|
| 574 |
|
| 575 | ret = nlm_traverse_files(server_addr, nlmsvc_match_ip, NULL);
|
| 576 | return ret ? -EIO : 0;
|
| 577 | }
|
| 578 | EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_ip);
|