summaryrefslogtreecommitdiffstats
path: root/fs/lockd
diff options
context:
space:
mode:
authorKees Cook <kees+treewide@kernel.org>2026-09-02 15:31:14 -0700
committerKees Cook <kees@kernel.org>2026-09-04 21:37:00 -0700
commit3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch)
treec65086f9bdcd48c6360fb7cb4598bca084da1f32 /fs/lockd
downloadlinux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.tar.gz
linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.zip
treewide: refresh kmalloc_obj() conversionsgrafted
This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook <kees+treewide@kernel.org>
Diffstat (limited to 'fs/lockd')
-rw-r--r--fs/lockd/Makefile52
-rw-r--r--fs/lockd/clnt4xdr.c582
-rw-r--r--fs/lockd/clntlock.c297
-rw-r--r--fs/lockd/clntproc.c892
-rw-r--r--fs/lockd/clntxdr.c614
-rw-r--r--fs/lockd/host.c723
-rw-r--r--fs/lockd/lockd.h483
-rw-r--r--fs/lockd/mon.c581
-rw-r--r--fs/lockd/netlink.c45
-rw-r--r--fs/lockd/netlink.h20
-rw-r--r--fs/lockd/netns.h25
-rw-r--r--fs/lockd/nlm.h56
-rw-r--r--fs/lockd/nlm3xdr_gen.c686
-rw-r--r--fs/lockd/nlm3xdr_gen.h30
-rw-r--r--fs/lockd/nlm4xdr_gen.c739
-rw-r--r--fs/lockd/nlm4xdr_gen.h30
-rw-r--r--fs/lockd/procfs.c92
-rw-r--r--fs/lockd/procfs.h27
-rw-r--r--fs/lockd/share.h39
-rw-r--r--fs/lockd/svc.c791
-rw-r--r--fs/lockd/svc4proc.c1432
-rw-r--r--fs/lockd/svclock.c1042
-rw-r--r--fs/lockd/svcproc.c1453
-rw-r--r--fs/lockd/svcshare.c151
-rw-r--r--fs/lockd/svcsubs.c578
-rw-r--r--fs/lockd/trace.c3
-rw-r--r--fs/lockd/trace.h107
-rw-r--r--fs/lockd/xdr.h85
28 files changed, 11655 insertions, 0 deletions
diff --git a/fs/lockd/Makefile b/fs/lockd/Makefile
new file mode 100644
index 000000000..3f9569d3b
--- /dev/null
+++ b/fs/lockd/Makefile
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the linux lock manager stuff
+#
+
+ccflags-y += -I$(src) # needed for trace events
+
+obj-$(CONFIG_LOCKD) += lockd.o
+
+lockd-y := clntlock.o clntproc.o clntxdr.o host.o svc.o svclock.o \
+ svcshare.o svcproc.o svcsubs.o mon.o trace.o netlink.o \
+ nlm3xdr_gen.o
+lockd-$(CONFIG_LOCKD_V4) += clnt4xdr.o svc4proc.o nlm4xdr_gen.o
+lockd-$(CONFIG_PROC_FS) += procfs.o
+
+#
+# XDR code generation (requires Python and additional packages)
+#
+# The generated *xdr_gen.{h,c} files are checked into git. Normal kernel
+# builds do not require the xdrgen tool or its Python dependencies.
+#
+# Developers modifying .x files in Documentation/sunrpc/xdr/ should run
+# "make xdrgen" to regenerate the affected files.
+#
+.PHONY: xdrgen
+
+XDRGEN = ../../tools/net/sunrpc/xdrgen/xdrgen
+
+XDRGEN_DEFINITIONS = ../../include/linux/sunrpc/xdrgen/nlm4.h \
+ ../../include/linux/sunrpc/xdrgen/nlm3.h
+XDRGEN_DECLARATIONS = nlm4xdr_gen.h nlm3xdr_gen.h
+XDRGEN_SOURCE = nlm4xdr_gen.c nlm3xdr_gen.c
+
+xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE)
+
+../../include/linux/sunrpc/xdrgen/nlm4.h: ../../Documentation/sunrpc/xdr/nlm4.x
+ $(XDRGEN) definitions $< > $@
+
+../../include/linux/sunrpc/xdrgen/nlm3.h: ../../Documentation/sunrpc/xdr/nlm3.x
+ $(XDRGEN) definitions $< > $@
+
+nlm4xdr_gen.h: ../../Documentation/sunrpc/xdr/nlm4.x
+ $(XDRGEN) declarations $< > $@
+
+nlm3xdr_gen.h: ../../Documentation/sunrpc/xdr/nlm3.x
+ $(XDRGEN) declarations $< > $@
+
+nlm4xdr_gen.c: ../../Documentation/sunrpc/xdr/nlm4.x
+ $(XDRGEN) source --peer server $< > $@
+
+nlm3xdr_gen.c: ../../Documentation/sunrpc/xdr/nlm3.x
+ $(XDRGEN) source --peer server $< > $@
diff --git a/fs/lockd/clnt4xdr.c b/fs/lockd/clnt4xdr.c
new file mode 100644
index 000000000..96a4a1e6a
--- /dev/null
+++ b/fs/lockd/clnt4xdr.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/clnt4xdr.c
+ *
+ * XDR functions to encode/decode NLM version 4 RPC arguments and results.
+ *
+ * NLM client-side only.
+ *
+ * Copyright (C) 2010, Oracle. All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/stats.h>
+
+#include "lockd.h"
+
+#include <uapi/linux/nfs3.h>
+
+#define NLMDBG_FACILITY NLMDBG_XDR
+
+#if (NLMCLNT_OHSIZE > XDR_MAX_NETOBJ)
+# error "NLM host name cannot be larger than XDR_MAX_NETOBJ!"
+#endif
+
+#if (NLMCLNT_OHSIZE > NLM_MAXSTRLEN)
+# error "NLM host name cannot be larger than NLM's maximum string length!"
+#endif
+
+/*
+ * Declare the space requirements for NLM arguments and replies as
+ * number of 32bit-words
+ */
+#define NLM4_void_sz (0)
+#define NLM4_cookie_sz (1+(NLM_MAXCOOKIELEN>>2))
+#define NLM4_caller_sz (1+(NLMCLNT_OHSIZE>>2))
+#define NLM4_owner_sz (1+(NLMCLNT_OHSIZE>>2))
+#define NLM4_fhandle_sz (1+(NFS3_FHSIZE>>2))
+#define NLM4_lock_sz (5+NLM4_caller_sz+NLM4_owner_sz+NLM4_fhandle_sz)
+#define NLM4_holder_sz (6+NLM4_owner_sz)
+
+#define NLM4_testargs_sz (NLM4_cookie_sz+1+NLM4_lock_sz)
+#define NLM4_lockargs_sz (NLM4_cookie_sz+4+NLM4_lock_sz)
+#define NLM4_cancargs_sz (NLM4_cookie_sz+2+NLM4_lock_sz)
+#define NLM4_unlockargs_sz (NLM4_cookie_sz+NLM4_lock_sz)
+
+#define NLM4_testres_sz (NLM4_cookie_sz+1+NLM4_holder_sz)
+#define NLM4_res_sz (NLM4_cookie_sz+1)
+#define NLM4_norep_sz (0)
+
+
+static s64 loff_t_to_s64(loff_t offset)
+{
+ s64 res;
+
+ if (offset >= NLM4_OFFSET_MAX)
+ res = NLM4_OFFSET_MAX;
+ else if (offset <= -NLM4_OFFSET_MAX)
+ res = -NLM4_OFFSET_MAX;
+ else
+ res = offset;
+ return res;
+}
+
+static void nlm4_compute_offsets(const struct lockd_lock *lock,
+ u64 *l_offset, u64 *l_len)
+{
+ const struct file_lock *fl = &lock->fl;
+
+ *l_offset = loff_t_to_s64(fl->fl_start);
+ if (fl->fl_end == OFFSET_MAX)
+ *l_len = 0;
+ else
+ *l_len = loff_t_to_s64(fl->fl_end - fl->fl_start + 1);
+}
+
+/*
+ * Encode/decode NLMv4 basic data types
+ *
+ * Basic NLMv4 data types are defined in Appendix II, section 6.1.4
+ * of RFC 1813: "NFS Version 3 Protocol Specification" and in Chapter
+ * 10 of X/Open's "Protocols for Interworking: XNFS, Version 3W".
+ *
+ * Not all basic data types have their own encoding and decoding
+ * functions. For run-time efficiency, some data types are encoded
+ * or decoded inline.
+ */
+
+static void encode_bool(struct xdr_stream *xdr, const int value)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4);
+ *p = value ? xdr_one : xdr_zero;
+}
+
+static void encode_int32(struct xdr_stream *xdr, const s32 value)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4);
+ *p = cpu_to_be32(value);
+}
+
+/*
+ * typedef opaque netobj<MAXNETOBJ_SZ>
+ */
+static void encode_netobj(struct xdr_stream *xdr,
+ const u8 *data, const unsigned int length)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4 + length);
+ xdr_encode_opaque(p, data, length);
+}
+
+static int decode_netobj(struct xdr_stream *xdr,
+ struct xdr_netobj *obj)
+{
+ ssize_t ret;
+
+ ret = xdr_stream_decode_opaque_inline(xdr, (void *)&obj->data,
+ XDR_MAX_NETOBJ);
+ if (unlikely(ret < 0))
+ return -EIO;
+ obj->len = ret;
+ return 0;
+}
+
+/*
+ * netobj cookie;
+ */
+static void encode_cookie(struct xdr_stream *xdr,
+ const struct lockd_cookie *cookie)
+{
+ encode_netobj(xdr, (u8 *)&cookie->data, cookie->len);
+}
+
+static int decode_cookie(struct xdr_stream *xdr,
+ struct lockd_cookie *cookie)
+{
+ u32 length;
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ length = be32_to_cpup(p++);
+ /* apparently HPUX can return empty cookies */
+ if (length == 0)
+ goto out_hpux;
+ if (length > NLM_MAXCOOKIELEN)
+ goto out_size;
+ p = xdr_inline_decode(xdr, length);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ cookie->len = length;
+ memcpy(cookie->data, p, length);
+ return 0;
+out_hpux:
+ cookie->len = 4;
+ memset(cookie->data, 0, 4);
+ return 0;
+out_size:
+ dprintk("NFS: returned cookie was too long: %u\n", length);
+ return -EIO;
+out_overflow:
+ return -EIO;
+}
+
+/*
+ * netobj fh;
+ */
+static void encode_fh(struct xdr_stream *xdr, const struct nfs_fh *fh)
+{
+ encode_netobj(xdr, (u8 *)&fh->data, fh->size);
+}
+
+/*
+ * enum nlm4_stats {
+ * NLM4_GRANTED = 0,
+ * NLM4_DENIED = 1,
+ * NLM4_DENIED_NOLOCKS = 2,
+ * NLM4_BLOCKED = 3,
+ * NLM4_DENIED_GRACE_PERIOD = 4,
+ * NLM4_DEADLCK = 5,
+ * NLM4_ROFS = 6,
+ * NLM4_STALE_FH = 7,
+ * NLM4_FBIG = 8,
+ * NLM4_FAILED = 9
+ * };
+ *
+ * struct nlm4_stat {
+ * nlm4_stats stat;
+ * };
+ *
+ * NB: we don't swap bytes for the NLM status values. The upper
+ * layers deal directly with the status value in network byte
+ * order.
+ */
+static void encode_nlm4_stat(struct xdr_stream *xdr,
+ const __be32 stat)
+{
+ __be32 *p;
+
+ BUG_ON(be32_to_cpu(stat) > NLM_FAILED);
+ p = xdr_reserve_space(xdr, 4);
+ *p = stat;
+}
+
+static int decode_nlm4_stat(struct xdr_stream *xdr, __be32 *stat)
+{
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ if (unlikely(ntohl(*p) > ntohl(nlm4_failed)))
+ goto out_bad_xdr;
+ *stat = *p;
+ return 0;
+out_bad_xdr:
+ dprintk("%s: server returned invalid nlm4_stats value: %u\n",
+ __func__, be32_to_cpup(p));
+ return -EIO;
+out_overflow:
+ return -EIO;
+}
+
+/*
+ * struct nlm4_holder {
+ * bool exclusive;
+ * int32 svid;
+ * netobj oh;
+ * uint64 l_offset;
+ * uint64 l_len;
+ * };
+ */
+static void encode_nlm4_holder(struct xdr_stream *xdr,
+ const struct lockd_res *result)
+{
+ const struct lockd_lock *lock = &result->lock;
+ u64 l_offset, l_len;
+ __be32 *p;
+
+ encode_bool(xdr, lock->fl.c.flc_type == F_RDLCK);
+ encode_int32(xdr, lock->svid);
+ encode_netobj(xdr, lock->oh.data, lock->oh.len);
+
+ p = xdr_reserve_space(xdr, 4 + 4);
+ nlm4_compute_offsets(lock, &l_offset, &l_len);
+ p = xdr_encode_hyper(p, l_offset);
+ xdr_encode_hyper(p, l_len);
+}
+
+static int decode_nlm4_holder(struct xdr_stream *xdr, struct lockd_res *result)
+{
+ struct lockd_lock *lock = &result->lock;
+ struct file_lock *fl = &lock->fl;
+ u64 l_offset, l_len;
+ u32 exclusive;
+ int error;
+ __be32 *p;
+
+ memset(lock, 0, sizeof(*lock));
+ locks_init_lock(fl);
+
+ p = xdr_inline_decode(xdr, 4 + 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ exclusive = be32_to_cpup(p++);
+ lock->svid = be32_to_cpup(p);
+ fl->c.flc_pid = (pid_t)lock->svid;
+
+ error = decode_netobj(xdr, &lock->oh);
+ if (unlikely(error))
+ goto out;
+
+ p = xdr_inline_decode(xdr, 8 + 8);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+
+ fl->c.flc_flags = FL_POSIX;
+ fl->c.flc_type = exclusive != 0 ? F_WRLCK : F_RDLCK;
+ p = xdr_decode_hyper(p, &l_offset);
+ xdr_decode_hyper(p, &l_len);
+ lockd_set_file_lock_range4(fl, l_offset, l_len);
+ error = 0;
+out:
+ return error;
+out_overflow:
+ return -EIO;
+}
+
+/*
+ * string caller_name<LM_MAXSTRLEN>;
+ */
+static void encode_caller_name(struct xdr_stream *xdr, const char *name)
+{
+ /* NB: client-side does not set lock->len */
+ u32 length = strlen(name);
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4 + length);
+ xdr_encode_opaque(p, name, length);
+}
+
+/*
+ * struct nlm4_lock {
+ * string caller_name<LM_MAXSTRLEN>;
+ * netobj fh;
+ * netobj oh;
+ * int32 svid;
+ * uint64 l_offset;
+ * uint64 l_len;
+ * };
+ */
+static void encode_nlm4_lock(struct xdr_stream *xdr,
+ const struct lockd_lock *lock)
+{
+ u64 l_offset, l_len;
+ __be32 *p;
+
+ encode_caller_name(xdr, lock->caller);
+ encode_fh(xdr, &lock->fh);
+ encode_netobj(xdr, lock->oh.data, lock->oh.len);
+
+ p = xdr_reserve_space(xdr, 4 + 8 + 8);
+ *p++ = cpu_to_be32(lock->svid);
+
+ nlm4_compute_offsets(lock, &l_offset, &l_len);
+ p = xdr_encode_hyper(p, l_offset);
+ xdr_encode_hyper(p, l_len);
+}
+
+
+/*
+ * NLMv4 XDR encode functions
+ *
+ * NLMv4 argument types are defined in Appendix II of RFC 1813:
+ * "NFS Version 3 Protocol Specification" and Chapter 10 of X/Open's
+ * "Protocols for Interworking: XNFS, Version 3W".
+ */
+
+/*
+ * struct nlm4_testargs {
+ * netobj cookie;
+ * bool exclusive;
+ * struct nlm4_lock alock;
+ * };
+ */
+static void nlm4_xdr_enc_testargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK);
+ encode_nlm4_lock(xdr, lock);
+}
+
+/*
+ * struct nlm4_lockargs {
+ * netobj cookie;
+ * bool block;
+ * bool exclusive;
+ * struct nlm4_lock alock;
+ * bool reclaim;
+ * int state;
+ * };
+ */
+static void nlm4_xdr_enc_lockargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_bool(xdr, args->block);
+ encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK);
+ encode_nlm4_lock(xdr, lock);
+ encode_bool(xdr, args->reclaim);
+ encode_int32(xdr, args->state);
+}
+
+/*
+ * struct nlm4_cancargs {
+ * netobj cookie;
+ * bool block;
+ * bool exclusive;
+ * struct nlm4_lock alock;
+ * };
+ */
+static void nlm4_xdr_enc_cancargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_bool(xdr, args->block);
+ encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK);
+ encode_nlm4_lock(xdr, lock);
+}
+
+/*
+ * struct nlm4_unlockargs {
+ * netobj cookie;
+ * struct nlm4_lock alock;
+ * };
+ */
+static void nlm4_xdr_enc_unlockargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_nlm4_lock(xdr, lock);
+}
+
+/*
+ * struct nlm4_res {
+ * netobj cookie;
+ * nlm4_stat stat;
+ * };
+ */
+static void nlm4_xdr_enc_res(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_res *result = data;
+
+ encode_cookie(xdr, &result->cookie);
+ encode_nlm4_stat(xdr, result->status);
+}
+
+/*
+ * union nlm4_testrply switch (nlm4_stats stat) {
+ * case NLM4_DENIED:
+ * struct nlm4_holder holder;
+ * default:
+ * void;
+ * };
+ *
+ * struct nlm4_testres {
+ * netobj cookie;
+ * nlm4_testrply test_stat;
+ * };
+ */
+static void nlm4_xdr_enc_testres(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_res *result = data;
+
+ encode_cookie(xdr, &result->cookie);
+ encode_nlm4_stat(xdr, result->status);
+ if (result->status == nlm_lck_denied)
+ encode_nlm4_holder(xdr, result);
+}
+
+
+/*
+ * NLMv4 XDR decode functions
+ *
+ * NLMv4 argument types are defined in Appendix II of RFC 1813:
+ * "NFS Version 3 Protocol Specification" and Chapter 10 of X/Open's
+ * "Protocols for Interworking: XNFS, Version 3W".
+ */
+
+/*
+ * union nlm4_testrply switch (nlm4_stats stat) {
+ * case NLM4_DENIED:
+ * struct nlm4_holder holder;
+ * default:
+ * void;
+ * };
+ *
+ * struct nlm4_testres {
+ * netobj cookie;
+ * nlm4_testrply test_stat;
+ * };
+ */
+static int decode_nlm4_testrply(struct xdr_stream *xdr,
+ struct lockd_res *result)
+{
+ int error;
+
+ error = decode_nlm4_stat(xdr, &result->status);
+ if (unlikely(error))
+ goto out;
+ if (result->status == nlm_lck_denied)
+ error = decode_nlm4_holder(xdr, result);
+out:
+ return error;
+}
+
+static int nlm4_xdr_dec_testres(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ void *data)
+{
+ struct lockd_res *result = data;
+ int error;
+
+ error = decode_cookie(xdr, &result->cookie);
+ if (unlikely(error))
+ goto out;
+ error = decode_nlm4_testrply(xdr, result);
+out:
+ return error;
+}
+
+/*
+ * struct nlm4_res {
+ * netobj cookie;
+ * nlm4_stat stat;
+ * };
+ */
+static int nlm4_xdr_dec_res(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ void *data)
+{
+ struct lockd_res *result = data;
+ int error;
+
+ error = decode_cookie(xdr, &result->cookie);
+ if (unlikely(error))
+ goto out;
+ error = decode_nlm4_stat(xdr, &result->status);
+out:
+ return error;
+}
+
+
+/*
+ * For NLM, a void procedure really returns nothing
+ */
+#define nlm4_xdr_dec_norep NULL
+
+#define PROC(proc, argtype, restype) \
+[NLMPROC_##proc] = { \
+ .p_proc = NLMPROC_##proc, \
+ .p_encode = nlm4_xdr_enc_##argtype, \
+ .p_decode = nlm4_xdr_dec_##restype, \
+ .p_arglen = NLM4_##argtype##_sz, \
+ .p_replen = NLM4_##restype##_sz, \
+ .p_statidx = NLMPROC_##proc, \
+ .p_name = #proc, \
+ }
+
+static const struct rpc_procinfo nlm4_procedures[] = {
+ PROC(TEST, testargs, testres),
+ PROC(LOCK, lockargs, res),
+ PROC(CANCEL, cancargs, res),
+ PROC(UNLOCK, unlockargs, res),
+ PROC(GRANTED, testargs, res),
+ PROC(TEST_MSG, testargs, norep),
+ PROC(LOCK_MSG, lockargs, norep),
+ PROC(CANCEL_MSG, cancargs, norep),
+ PROC(UNLOCK_MSG, unlockargs, norep),
+ PROC(GRANTED_MSG, testargs, norep),
+ PROC(TEST_RES, testres, norep),
+ PROC(LOCK_RES, res, norep),
+ PROC(CANCEL_RES, res, norep),
+ PROC(UNLOCK_RES, res, norep),
+ PROC(GRANTED_RES, res, norep),
+};
+
+static unsigned int nlm_version4_counts[ARRAY_SIZE(nlm4_procedures)];
+const struct rpc_version nlm_version4 = {
+ .number = 4,
+ .nrprocs = ARRAY_SIZE(nlm4_procedures),
+ .procs = nlm4_procedures,
+ .counts = nlm_version4_counts,
+};
diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c
new file mode 100644
index 000000000..f797cc99f
--- /dev/null
+++ b/fs/lockd/clntlock.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/fs/lockd/clntlock.c
+ *
+ * Lock handling for the client side NLM implementation
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/nfs_fs.h>
+#include <linux/sunrpc/addr.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/sunrpc/svc_xprt.h>
+#include <linux/kthread.h>
+
+#include "lockd.h"
+#include "trace.h"
+
+#define NLMDBG_FACILITY NLMDBG_CLIENT
+
+/*
+ * Local function prototypes
+ */
+static int reclaimer(void *ptr);
+
+/*
+ * The following functions handle blocking and granting from the
+ * client perspective.
+ */
+
+static LIST_HEAD(nlm_blocked);
+static DEFINE_SPINLOCK(nlm_blocked_lock);
+
+/**
+ * nlmclnt_init - Set up per-NFS mount point lockd data structures
+ * @nlm_init: pointer to arguments structure
+ *
+ * Returns pointer to an appropriate nlm_host struct,
+ * or an ERR_PTR value.
+ */
+struct nlm_host *nlmclnt_init(const struct nlmclnt_initdata *nlm_init)
+{
+ struct nlm_host *host;
+ u32 nlm_version = (nlm_init->nfs_version == 2) ? 1 : 4;
+ int status;
+
+ status = lockd_up(nlm_init->net, nlm_init->cred);
+ if (status < 0)
+ return ERR_PTR(status);
+
+ host = nlmclnt_lookup_host(nlm_init->address, nlm_init->addrlen,
+ nlm_init->protocol, nlm_version,
+ nlm_init->hostname, nlm_init->noresvport,
+ nlm_init->net, nlm_init->cred);
+ if (host == NULL)
+ goto out_nohost;
+ if (host->h_rpcclnt == NULL && nlm_bind_host(host) == NULL)
+ goto out_nobind;
+
+ host->h_nlmclnt_ops = nlm_init->nlmclnt_ops;
+ return host;
+out_nobind:
+ nlmclnt_release_host(host);
+out_nohost:
+ lockd_down(nlm_init->net);
+ return ERR_PTR(-ENOLCK);
+}
+EXPORT_SYMBOL_GPL(nlmclnt_init);
+
+/**
+ * nlmclnt_done - Release resources allocated by nlmclnt_init()
+ * @host: nlm_host structure reserved by nlmclnt_init()
+ *
+ */
+void nlmclnt_done(struct nlm_host *host)
+{
+ struct net *net = host->net;
+
+ nlmclnt_release_host(host);
+ lockd_down(net);
+}
+EXPORT_SYMBOL_GPL(nlmclnt_done);
+
+void nlmclnt_prepare_block(struct nlm_wait *block, struct nlm_host *host, struct file_lock *fl)
+{
+ block->b_host = host;
+ block->b_lock = fl;
+ init_waitqueue_head(&block->b_wait);
+ block->b_status = nlm_lck_blocked;
+}
+
+struct rpc_clnt *nlmclnt_rpc_clnt(struct nlm_host *host)
+{
+ return host->h_rpcclnt;
+}
+EXPORT_SYMBOL_GPL(nlmclnt_rpc_clnt);
+
+/*
+ * Queue up a lock for blocking so that the GRANTED request can see it
+ */
+void nlmclnt_queue_block(struct nlm_wait *block)
+{
+ spin_lock(&nlm_blocked_lock);
+ list_add(&block->b_list, &nlm_blocked);
+ spin_unlock(&nlm_blocked_lock);
+}
+
+/*
+ * Dequeue the block and return its final status
+ */
+__be32 nlmclnt_dequeue_block(struct nlm_wait *block)
+{
+ __be32 status;
+
+ spin_lock(&nlm_blocked_lock);
+ list_del(&block->b_list);
+ status = block->b_status;
+ spin_unlock(&nlm_blocked_lock);
+ return status;
+}
+
+/*
+ * Block on a lock
+ */
+int nlmclnt_wait(struct nlm_wait *block, struct nlm_rqst *req, long timeout)
+{
+ long ret;
+
+ /* A borken server might ask us to block even if we didn't
+ * request it. Just say no!
+ */
+ if (block == NULL)
+ return -EAGAIN;
+
+ /* Go to sleep waiting for GRANT callback. Some servers seem
+ * to lose callbacks, however, so we're going to poll from
+ * time to time just to make sure.
+ *
+ * For now, the retry frequency is pretty high; normally
+ * a 1 minute timeout would do. See the comment before
+ * nlmclnt_lock for an explanation.
+ */
+ ret = wait_event_interruptible_timeout(block->b_wait,
+ block->b_status != nlm_lck_blocked,
+ timeout);
+ if (ret < 0)
+ return -ERESTARTSYS;
+ /* Reset the lock status after a server reboot so we resend */
+ if (block->b_status == nlm_lck_denied_grace_period)
+ block->b_status = nlm_lck_blocked;
+ return 0;
+}
+
+/*
+ * The server lockd has called us back to tell us the lock was granted
+ */
+__be32 nlmclnt_grant(const struct sockaddr *addr, const struct lockd_lock *lock)
+{
+ const struct file_lock *fl = &lock->fl;
+ const struct nfs_fh *fh = &lock->fh;
+ struct nlm_wait *block;
+ __be32 res = nlm_lck_denied;
+
+ /*
+ * Look up blocked request based on arguments.
+ * Warning: must not use cookie to match it!
+ */
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ struct file_lock *fl_blocked = block->b_lock;
+
+ if (fl_blocked->fl_start != fl->fl_start)
+ continue;
+ if (fl_blocked->fl_end != fl->fl_end)
+ continue;
+ /*
+ * Careful! The NLM server will return the 32-bit "pid" that
+ * we put on the wire: in this case the lockowner "pid".
+ */
+ if (fl_blocked->fl_u.nfs_fl.owner->pid != lock->svid)
+ continue;
+ if (!rpc_cmp_addr(nlm_addr(block->b_host), addr))
+ continue;
+ if (nfs_compare_fh(NFS_FH(file_inode(fl_blocked->c.flc_file)), fh) != 0)
+ continue;
+ /* Alright, we found a lock. Set the return status
+ * and wake up the caller
+ */
+ block->b_status = nlm_granted;
+ wake_up(&block->b_wait);
+ res = nlm_granted;
+ }
+ spin_unlock(&nlm_blocked_lock);
+ trace_nlmclnt_grant(lock, addr, svc_addr_len(addr), res);
+ return res;
+}
+
+/*
+ * The following procedures deal with the recovery of locks after a
+ * server crash.
+ */
+
+/*
+ * Reclaim all locks on server host. We do this by spawning a separate
+ * reclaimer thread.
+ */
+void
+nlmclnt_recovery(struct nlm_host *host)
+{
+ struct task_struct *task;
+
+ if (!host->h_reclaiming++) {
+ nlm_get_host(host);
+ task = kthread_run(reclaimer, host, "%s-reclaim", host->h_name);
+ if (IS_ERR(task))
+ printk(KERN_ERR "lockd: unable to spawn reclaimer "
+ "thread. Locks for %s won't be reclaimed! "
+ "(%ld)\n", host->h_name, PTR_ERR(task));
+ }
+}
+
+static int
+reclaimer(void *ptr)
+{
+ struct nlm_host *host = (struct nlm_host *) ptr;
+ struct nlm_wait *block;
+ struct nlm_rqst *req;
+ struct file_lock *fl, *next;
+ u32 nsmstate;
+ struct net *net = host->net;
+
+ req = kmalloc_obj(*req);
+ if (!req)
+ return 0;
+
+ allow_signal(SIGKILL);
+
+ down_write(&host->h_rwsem);
+ lockd_up(net, NULL); /* note: this cannot fail as lockd is already running */
+
+ dprintk("lockd: reclaiming locks for host %s\n", host->h_name);
+
+restart:
+ nsmstate = host->h_nsmstate;
+
+ /* Force a portmap getport - the peer's lockd will
+ * most likely end up on a different port.
+ */
+ host->h_nextrebind = jiffies;
+ nlm_rebind_host(host);
+
+ /* First, reclaim all locks that have been granted. */
+ list_splice_init(&host->h_granted, &host->h_reclaim);
+ list_for_each_entry_safe(fl, next, &host->h_reclaim, fl_u.nfs_fl.list) {
+ list_del_init(&fl->fl_u.nfs_fl.list);
+
+ /*
+ * sending this thread a SIGKILL will result in any unreclaimed
+ * locks being removed from the h_granted list. This means that
+ * the kernel will not attempt to reclaim them again if a new
+ * reclaimer thread is spawned for this host.
+ */
+ if (signalled())
+ continue;
+ if (nlmclnt_reclaim(host, fl, req) != 0)
+ continue;
+ list_add_tail(&fl->fl_u.nfs_fl.list, &host->h_granted);
+ if (host->h_nsmstate != nsmstate) {
+ /* Argh! The server rebooted again! */
+ goto restart;
+ }
+ }
+
+ host->h_reclaiming = 0;
+ up_write(&host->h_rwsem);
+ dprintk("NLM: done reclaiming locks for host %s\n", host->h_name);
+
+ /* Now, wake up all processes that sleep on a blocked lock */
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ if (block->b_host == host) {
+ block->b_status = nlm_lck_denied_grace_period;
+ wake_up(&block->b_wait);
+ }
+ }
+ spin_unlock(&nlm_blocked_lock);
+
+ /* Release host handle after use */
+ nlmclnt_release_host(host);
+ lockd_down(net);
+ kfree(req);
+ return 0;
+}
diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c
new file mode 100644
index 000000000..f8018bfe9
--- /dev/null
+++ b/fs/lockd/clntproc.c
@@ -0,0 +1,892 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/fs/lockd/clntproc.c
+ *
+ * RPC procedures for the client side NLM implementation
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/filelock.h>
+#include <linux/nfs_fs.h>
+#include <linux/utsname.h>
+#include <linux/freezer.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc.h>
+
+#include "lockd.h"
+#include "trace.h"
+
+#define NLMDBG_FACILITY NLMDBG_CLIENT
+#define NLMCLNT_GRACE_WAIT (5*HZ)
+#define NLMCLNT_POLL_TIMEOUT (30*HZ)
+#define NLMCLNT_MAX_RETRIES 3
+
+static int nlmclnt_test(struct nlm_rqst *, struct file_lock *);
+static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *);
+static int nlmclnt_unlock(struct nlm_rqst *, struct file_lock *);
+static int nlm_stat_to_errno(__be32 stat);
+static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host);
+static int nlmclnt_cancel(struct nlm_host *, int , struct file_lock *);
+
+static const struct rpc_call_ops nlmclnt_unlock_ops;
+static const struct rpc_call_ops nlmclnt_cancel_ops;
+
+/*
+ * Cookie counter for NLM requests
+ */
+static atomic_t nlm_cookie = ATOMIC_INIT(0x1234);
+
+void nlmclnt_next_cookie(struct lockd_cookie *c)
+{
+ u32 cookie = atomic_inc_return(&nlm_cookie);
+
+ memcpy(c->data, &cookie, 4);
+ c->len=4;
+}
+
+static struct nlm_lockowner *
+nlmclnt_get_lockowner(struct nlm_lockowner *lockowner)
+{
+ refcount_inc(&lockowner->count);
+ return lockowner;
+}
+
+static void nlmclnt_put_lockowner(struct nlm_lockowner *lockowner)
+{
+ if (!refcount_dec_and_lock(&lockowner->count, &lockowner->host->h_lock))
+ return;
+ list_del(&lockowner->list);
+ spin_unlock(&lockowner->host->h_lock);
+ nlmclnt_release_host(lockowner->host);
+ kfree(lockowner);
+}
+
+static inline int nlm_pidbusy(struct nlm_host *host, uint32_t pid)
+{
+ struct nlm_lockowner *lockowner;
+ list_for_each_entry(lockowner, &host->h_lockowners, list) {
+ if (lockowner->pid == pid)
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static inline uint32_t __nlm_alloc_pid(struct nlm_host *host)
+{
+ uint32_t res;
+ do {
+ res = host->h_pidcount++;
+ } while (nlm_pidbusy(host, res) < 0);
+ return res;
+}
+
+static struct nlm_lockowner *__nlmclnt_find_lockowner(struct nlm_host *host, fl_owner_t owner)
+{
+ struct nlm_lockowner *lockowner;
+ list_for_each_entry(lockowner, &host->h_lockowners, list) {
+ if (lockowner->owner != owner)
+ continue;
+ return nlmclnt_get_lockowner(lockowner);
+ }
+ return NULL;
+}
+
+static struct nlm_lockowner *nlmclnt_find_lockowner(struct nlm_host *host, fl_owner_t owner)
+{
+ struct nlm_lockowner *res, *new = NULL;
+
+ spin_lock(&host->h_lock);
+ res = __nlmclnt_find_lockowner(host, owner);
+ if (res == NULL) {
+ spin_unlock(&host->h_lock);
+ new = kmalloc_obj(*new);
+ spin_lock(&host->h_lock);
+ res = __nlmclnt_find_lockowner(host, owner);
+ if (res == NULL && new != NULL) {
+ res = new;
+ refcount_set(&new->count, 1);
+ new->owner = owner;
+ new->pid = __nlm_alloc_pid(host);
+ new->host = nlm_get_host(host);
+ list_add(&new->list, &host->h_lockowners);
+ new = NULL;
+ }
+ }
+ spin_unlock(&host->h_lock);
+ kfree(new);
+ return res;
+}
+
+/*
+ * Initialize arguments for TEST/LOCK/UNLOCK/CANCEL calls
+ */
+static void nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl)
+{
+ struct lockd_args *argp = &req->a_args;
+ struct lockd_lock *lock = &argp->lock;
+ char *nodename = req->a_host->h_rpcclnt->cl_nodename;
+
+ nlmclnt_next_cookie(&argp->cookie);
+ memcpy(&lock->fh, NFS_FH(file_inode(fl->c.flc_file)),
+ sizeof(struct nfs_fh));
+ lock->caller = nodename;
+ lock->oh.data = req->a_owner;
+ lock->oh.len = snprintf(req->a_owner, sizeof(req->a_owner), "%u@%s",
+ (unsigned int)fl->fl_u.nfs_fl.owner->pid,
+ nodename);
+ lock->svid = fl->fl_u.nfs_fl.owner->pid;
+ lock->fl.fl_start = fl->fl_start;
+ lock->fl.fl_end = fl->fl_end;
+ lock->fl.c.flc_type = fl->c.flc_type;
+}
+
+static void nlmclnt_release_lockargs(struct nlm_rqst *req)
+{
+ WARN_ON_ONCE(req->a_args.lock.fl.fl_ops != NULL);
+}
+
+/**
+ * nlmclnt_proc - Perform a single client-side lock request
+ * @host: address of a valid nlm_host context representing the NLM server
+ * @cmd: fcntl-style file lock operation to perform
+ * @fl: address of arguments for the lock operation
+ * @data: address of data to be sent to callback operations
+ *
+ */
+int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl, void *data)
+{
+ struct nlm_rqst *call;
+ int status;
+ const struct nlmclnt_operations *nlmclnt_ops = host->h_nlmclnt_ops;
+
+ call = nlm_alloc_call(host);
+ if (call == NULL)
+ return -ENOMEM;
+
+ if (nlmclnt_ops && nlmclnt_ops->nlmclnt_alloc_call)
+ nlmclnt_ops->nlmclnt_alloc_call(data);
+
+ nlmclnt_locks_init_private(fl, host);
+ if (!fl->fl_u.nfs_fl.owner) {
+ /* lockowner allocation has failed */
+ nlmclnt_release_call(call);
+ return -ENOMEM;
+ }
+ /* Set up the argument struct */
+ nlmclnt_setlockargs(call, fl);
+ call->a_callback_data = data;
+
+ if (IS_SETLK(cmd) || IS_SETLKW(cmd)) {
+ if (fl->c.flc_type != F_UNLCK) {
+ call->a_args.block = IS_SETLKW(cmd) ? 1 : 0;
+ status = nlmclnt_lock(call, fl);
+ } else
+ status = nlmclnt_unlock(call, fl);
+ } else if (IS_GETLK(cmd))
+ status = nlmclnt_test(call, fl);
+ else
+ status = -EINVAL;
+ fl->fl_ops->fl_release_private(fl);
+ fl->fl_ops = NULL;
+
+ dprintk("lockd: clnt proc returns %d\n", status);
+ return status;
+}
+EXPORT_SYMBOL_GPL(nlmclnt_proc);
+
+/*
+ * Allocate an NLM RPC call struct
+ */
+struct nlm_rqst *nlm_alloc_call(struct nlm_host *host)
+{
+ struct nlm_rqst *call;
+
+ for(;;) {
+ call = kzalloc_obj(*call);
+ if (call != NULL) {
+ refcount_set(&call->a_count, 1);
+ locks_init_lock(&call->a_args.lock.fl);
+ locks_init_lock(&call->a_res.lock.fl);
+ call->a_host = nlm_get_host(host);
+ return call;
+ }
+ if (signalled())
+ break;
+ printk("nlm_alloc_call: failed, waiting for memory\n");
+ schedule_timeout_interruptible(5*HZ);
+ }
+ return NULL;
+}
+
+void nlmclnt_release_call(struct nlm_rqst *call)
+{
+ const struct nlmclnt_operations *nlmclnt_ops = call->a_host->h_nlmclnt_ops;
+
+ if (!refcount_dec_and_test(&call->a_count))
+ return;
+ if (nlmclnt_ops && nlmclnt_ops->nlmclnt_release_call)
+ nlmclnt_ops->nlmclnt_release_call(call->a_callback_data);
+ nlmclnt_release_host(call->a_host);
+ nlmclnt_release_lockargs(call);
+ kfree(call);
+}
+
+static void nlmclnt_rpc_release(void *data)
+{
+ nlmclnt_release_call(data);
+}
+
+static int nlm_wait_on_grace(wait_queue_head_t *queue)
+{
+ DEFINE_WAIT(wait);
+ int status = -EINTR;
+
+ prepare_to_wait(queue, &wait, TASK_INTERRUPTIBLE);
+ if (!signalled ()) {
+ schedule_timeout(NLMCLNT_GRACE_WAIT);
+ try_to_freeze();
+ if (!signalled ())
+ status = 0;
+ }
+ finish_wait(queue, &wait);
+ return status;
+}
+
+/*
+ * Generic NLM call
+ */
+static int
+nlmclnt_call(const struct cred *cred, struct nlm_rqst *req, u32 proc)
+{
+ struct nlm_host *host = req->a_host;
+ struct rpc_clnt *clnt;
+ struct lockd_args *argp = &req->a_args;
+ struct lockd_res *resp = &req->a_res;
+ struct rpc_message msg = {
+ .rpc_argp = argp,
+ .rpc_resp = resp,
+ .rpc_cred = cred,
+ };
+ int status;
+
+ dprintk("lockd: call procedure %d on %s\n",
+ (int)proc, host->h_name);
+
+ do {
+ if (host->h_reclaiming && !argp->reclaim)
+ goto in_grace_period;
+
+ /* If we have no RPC client yet, create one. */
+ if ((clnt = nlm_bind_host(host)) == NULL)
+ return -ENOLCK;
+ msg.rpc_proc = &clnt->cl_procinfo[proc];
+
+ /* Perform the RPC call. If an error occurs, try again */
+ if ((status = rpc_call_sync(clnt, &msg, 0)) < 0) {
+ dprintk("lockd: rpc_call returned error %d\n", -status);
+ switch (status) {
+ case -EPROTONOSUPPORT:
+ status = -EINVAL;
+ break;
+ case -ECONNREFUSED:
+ case -ETIMEDOUT:
+ case -ENOTCONN:
+ nlm_rebind_host(host);
+ status = -EAGAIN;
+ break;
+ case -ERESTARTSYS:
+ return signalled () ? -EINTR : status;
+ default:
+ break;
+ }
+ break;
+ } else
+ if (resp->status == nlm_lck_denied_grace_period) {
+ dprintk("lockd: server in grace period\n");
+ if (argp->reclaim) {
+ printk(KERN_WARNING
+ "lockd: spurious grace period reject?!\n");
+ return -ENOLCK;
+ }
+ } else {
+ if (!argp->reclaim) {
+ /* We appear to be out of the grace period */
+ wake_up_all(&host->h_gracewait);
+ }
+ dprintk("lockd: server returns status %d\n",
+ ntohl(resp->status));
+ return 0; /* Okay, call complete */
+ }
+
+in_grace_period:
+ /*
+ * The server has rebooted and appears to be in the grace
+ * period during which locks are only allowed to be
+ * reclaimed.
+ * We can only back off and try again later.
+ */
+ status = nlm_wait_on_grace(&host->h_gracewait);
+ } while (status == 0);
+
+ return status;
+}
+
+/*
+ * Generic NLM call, async version.
+ */
+static struct rpc_task *__nlm_async_call(struct nlm_rqst *req, u32 proc, struct rpc_message *msg, const struct rpc_call_ops *tk_ops)
+{
+ struct nlm_host *host = req->a_host;
+ struct rpc_clnt *clnt;
+ struct rpc_task_setup task_setup_data = {
+ .rpc_message = msg,
+ .callback_ops = tk_ops,
+ .callback_data = req,
+ .flags = RPC_TASK_ASYNC,
+ };
+
+ dprintk("lockd: call procedure %d on %s (async)\n",
+ (int)proc, host->h_name);
+
+ /* If we have no RPC client yet, create one. */
+ clnt = nlm_bind_host(host);
+ if (clnt == NULL)
+ goto out_err;
+ msg->rpc_proc = &clnt->cl_procinfo[proc];
+ task_setup_data.rpc_client = clnt;
+
+ /* bootstrap and kick off the async RPC call */
+ return rpc_run_task(&task_setup_data);
+out_err:
+ tk_ops->rpc_release(req);
+ return ERR_PTR(-ENOLCK);
+}
+
+static int nlm_do_async_call(struct nlm_rqst *req, u32 proc, struct rpc_message *msg, const struct rpc_call_ops *tk_ops)
+{
+ struct rpc_task *task;
+
+ task = __nlm_async_call(req, proc, msg, tk_ops);
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+ rpc_put_task(task);
+ return 0;
+}
+
+/*
+ * NLM asynchronous call.
+ */
+int nlm_async_call(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops)
+{
+ struct rpc_message msg = {
+ .rpc_argp = &req->a_args,
+ .rpc_resp = &req->a_res,
+ };
+ return nlm_do_async_call(req, proc, &msg, tk_ops);
+}
+
+int nlm_async_reply(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops)
+{
+ struct rpc_message msg = {
+ .rpc_argp = &req->a_res,
+ };
+ return nlm_do_async_call(req, proc, &msg, tk_ops);
+}
+
+/*
+ * NLM client asynchronous call.
+ *
+ * Note that although the calls are asynchronous, and are therefore
+ * guaranteed to complete, we still always attempt to wait for
+ * completion in order to be able to correctly track the lock
+ * state.
+ */
+static int nlmclnt_async_call(const struct cred *cred, struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops)
+{
+ struct rpc_message msg = {
+ .rpc_argp = &req->a_args,
+ .rpc_resp = &req->a_res,
+ .rpc_cred = cred,
+ };
+ struct rpc_task *task;
+ int err;
+
+ task = __nlm_async_call(req, proc, &msg, tk_ops);
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+ err = rpc_wait_for_completion_task(task);
+ rpc_put_task(task);
+ return err;
+}
+
+/*
+ * TEST for the presence of a conflicting lock
+ */
+static int
+nlmclnt_test(struct nlm_rqst *req, struct file_lock *fl)
+{
+ int status;
+
+ status = nlmclnt_call(nfs_file_cred(fl->c.flc_file), req,
+ NLMPROC_TEST);
+ if (status < 0)
+ goto out;
+
+ switch (req->a_res.status) {
+ case nlm_granted:
+ fl->c.flc_type = F_UNLCK;
+ break;
+ case nlm_lck_denied:
+ /*
+ * Report the conflicting lock back to the application.
+ */
+ fl->fl_start = req->a_res.lock.fl.fl_start;
+ fl->fl_end = req->a_res.lock.fl.fl_end;
+ fl->c.flc_type = req->a_res.lock.fl.c.flc_type;
+ fl->c.flc_pid = -req->a_res.lock.fl.c.flc_pid;
+ break;
+ default:
+ status = nlm_stat_to_errno(req->a_res.status);
+ }
+out:
+ trace_nlmclnt_test(&req->a_args.lock,
+ (const struct sockaddr *)&req->a_host->h_addr,
+ req->a_host->h_addrlen, req->a_res.status);
+ nlmclnt_release_call(req);
+ return status;
+}
+
+static void nlmclnt_locks_copy_lock(struct file_lock *new, struct file_lock *fl)
+{
+ spin_lock(&fl->fl_u.nfs_fl.owner->host->h_lock);
+ new->fl_u.nfs_fl.state = fl->fl_u.nfs_fl.state;
+ new->fl_u.nfs_fl.owner = nlmclnt_get_lockowner(fl->fl_u.nfs_fl.owner);
+ list_add_tail(&new->fl_u.nfs_fl.list, &fl->fl_u.nfs_fl.owner->host->h_granted);
+ spin_unlock(&fl->fl_u.nfs_fl.owner->host->h_lock);
+}
+
+static void nlmclnt_locks_release_private(struct file_lock *fl)
+{
+ spin_lock(&fl->fl_u.nfs_fl.owner->host->h_lock);
+ list_del(&fl->fl_u.nfs_fl.list);
+ spin_unlock(&fl->fl_u.nfs_fl.owner->host->h_lock);
+ nlmclnt_put_lockowner(fl->fl_u.nfs_fl.owner);
+}
+
+static const struct file_lock_operations nlmclnt_lock_ops = {
+ .fl_copy_lock = nlmclnt_locks_copy_lock,
+ .fl_release_private = nlmclnt_locks_release_private,
+};
+
+static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host)
+{
+ fl->fl_u.nfs_fl.state = 0;
+ fl->fl_ops = NULL;
+ fl->fl_u.nfs_fl.owner = nlmclnt_find_lockowner(host,
+ fl->c.flc_owner);
+ INIT_LIST_HEAD(&fl->fl_u.nfs_fl.list);
+ if (!fl->fl_u.nfs_fl.owner)
+ return;
+ fl->fl_ops = &nlmclnt_lock_ops;
+}
+
+static int do_vfs_lock(struct file_lock *fl)
+{
+ return locks_lock_file_wait(fl->c.flc_file, fl);
+}
+
+/*
+ * LOCK: Try to create a lock
+ *
+ * Programmer Harassment Alert
+ *
+ * When given a blocking lock request in a sync RPC call, the HPUX lockd
+ * will faithfully return LCK_BLOCKED but never cares to notify us when
+ * the lock could be granted. This way, our local process could hang
+ * around forever waiting for the callback.
+ *
+ * Solution A: Implement busy-waiting
+ * Solution B: Use the async version of the call (NLM_LOCK_{MSG,RES})
+ *
+ * For now I am implementing solution A, because I hate the idea of
+ * re-implementing lockd for a third time in two months. The async
+ * calls shouldn't be too hard to do, however.
+ *
+ * This is one of the lovely things about standards in the NFS area:
+ * they're so soft and squishy you can't really blame HP for doing this.
+ */
+static int
+nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl)
+{
+ const struct cred *cred = nfs_file_cred(fl->c.flc_file);
+ struct nlm_host *host = req->a_host;
+ struct lockd_res *resp = &req->a_res;
+ struct nlm_wait block;
+ unsigned char flags = fl->c.flc_flags;
+ unsigned char type;
+ __be32 b_status;
+ int status = -ENOLCK;
+
+ if (nsm_monitor(host) < 0)
+ goto out;
+ req->a_args.state = nsm_local_state;
+
+ fl->c.flc_flags |= FL_ACCESS;
+ status = do_vfs_lock(fl);
+ fl->c.flc_flags = flags;
+ if (status < 0)
+ goto out;
+
+ nlmclnt_prepare_block(&block, host, fl);
+again:
+ /*
+ * Initialise resp->status to a valid non-zero value,
+ * since 0 == nlm_lck_granted
+ */
+ resp->status = nlm_lck_blocked;
+
+ /*
+ * A GRANTED callback can come at any time -- even before the reply
+ * to the LOCK request arrives, so we queue the wait before
+ * requesting the lock.
+ */
+ nlmclnt_queue_block(&block);
+ for (;;) {
+ /* Reboot protection */
+ fl->fl_u.nfs_fl.state = host->h_state;
+ status = nlmclnt_call(cred, req, NLMPROC_LOCK);
+ if (status < 0)
+ break;
+ /* Did a reclaimer thread notify us of a server reboot? */
+ if (resp->status == nlm_lck_denied_grace_period)
+ continue;
+ if (resp->status != nlm_lck_blocked)
+ break;
+ /* Wait on an NLM blocking lock */
+ status = nlmclnt_wait(&block, req, NLMCLNT_POLL_TIMEOUT);
+ if (status < 0)
+ break;
+ if (block.b_status != nlm_lck_blocked)
+ break;
+ }
+ b_status = nlmclnt_dequeue_block(&block);
+ if (resp->status == nlm_lck_blocked)
+ resp->status = b_status;
+
+ /* if we were interrupted while blocking, then cancel the lock request
+ * and exit
+ */
+ if (resp->status == nlm_lck_blocked) {
+ if (!req->a_args.block)
+ goto out_unlock;
+ if (nlmclnt_cancel(host, req->a_args.block, fl) == 0)
+ goto out;
+ }
+
+ if (resp->status == nlm_granted) {
+ down_read(&host->h_rwsem);
+ /* Check whether or not the server has rebooted */
+ if (fl->fl_u.nfs_fl.state != host->h_state) {
+ up_read(&host->h_rwsem);
+ goto again;
+ }
+ /* Ensure the resulting lock will get added to granted list */
+ fl->c.flc_flags |= FL_SLEEP;
+ if (do_vfs_lock(fl) < 0)
+ printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", __func__);
+ up_read(&host->h_rwsem);
+ fl->c.flc_flags = flags;
+ status = 0;
+ }
+ if (status < 0)
+ goto out_unlock;
+ /*
+ * EAGAIN doesn't make sense for sleeping locks, and in some
+ * cases NLM_LCK_DENIED is returned for a permanent error. So
+ * turn it into an ENOLCK.
+ */
+ if (resp->status == nlm_lck_denied && (flags & FL_SLEEP))
+ status = -ENOLCK;
+ else
+ status = nlm_stat_to_errno(resp->status);
+out:
+ trace_nlmclnt_lock(&req->a_args.lock,
+ (const struct sockaddr *)&req->a_host->h_addr,
+ req->a_host->h_addrlen, req->a_res.status);
+ nlmclnt_release_call(req);
+ return status;
+out_unlock:
+ /* Fatal error: ensure that we remove the lock altogether */
+ trace_nlmclnt_lock(&req->a_args.lock,
+ (const struct sockaddr *)&req->a_host->h_addr,
+ req->a_host->h_addrlen, req->a_res.status);
+ dprintk("lockd: lock attempt ended in fatal error.\n"
+ " Attempting to unlock.\n");
+ type = fl->c.flc_type;
+ fl->c.flc_type = F_UNLCK;
+ down_read(&host->h_rwsem);
+ do_vfs_lock(fl);
+ up_read(&host->h_rwsem);
+ fl->c.flc_type = type;
+ fl->c.flc_flags = flags;
+ nlmclnt_async_call(cred, req, NLMPROC_UNLOCK, &nlmclnt_unlock_ops);
+ return status;
+}
+
+/*
+ * RECLAIM: Try to reclaim a lock
+ */
+int
+nlmclnt_reclaim(struct nlm_host *host, struct file_lock *fl,
+ struct nlm_rqst *req)
+{
+ int status;
+
+ memset(req, 0, sizeof(*req));
+ locks_init_lock(&req->a_args.lock.fl);
+ locks_init_lock(&req->a_res.lock.fl);
+ req->a_host = host;
+
+ /* Set up the argument struct */
+ nlmclnt_setlockargs(req, fl);
+ req->a_args.reclaim = 1;
+
+ status = nlmclnt_call(nfs_file_cred(fl->c.flc_file), req,
+ NLMPROC_LOCK);
+ if (status >= 0 && req->a_res.status == nlm_granted)
+ return 0;
+
+ printk(KERN_WARNING "lockd: failed to reclaim lock for pid %d "
+ "(errno %d, status %d)\n",
+ fl->c.flc_pid,
+ status, ntohl(req->a_res.status));
+
+ /*
+ * FIXME: This is a serious failure. We can
+ *
+ * a. Ignore the problem
+ * b. Send the owning process some signal (Linux doesn't have
+ * SIGLOST, though...)
+ * c. Retry the operation
+ *
+ * Until someone comes up with a simple implementation
+ * for b or c, I'll choose option a.
+ */
+
+ return -ENOLCK;
+}
+
+/*
+ * UNLOCK: remove an existing lock
+ */
+static int
+nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl)
+{
+ struct nlm_host *host = req->a_host;
+ struct lockd_res *resp = &req->a_res;
+ int status;
+ unsigned char flags = fl->c.flc_flags;
+
+ /*
+ * Note: the server is supposed to either grant us the unlock
+ * request, or to deny it with NLM_LCK_DENIED_GRACE_PERIOD. In either
+ * case, we want to unlock.
+ */
+ fl->c.flc_flags |= FL_EXISTS;
+ down_read(&host->h_rwsem);
+ status = do_vfs_lock(fl);
+ up_read(&host->h_rwsem);
+ fl->c.flc_flags = flags;
+ if (status == -ENOENT) {
+ status = 0;
+ goto out;
+ }
+
+ refcount_inc(&req->a_count);
+ status = nlmclnt_async_call(nfs_file_cred(fl->c.flc_file), req,
+ NLMPROC_UNLOCK, &nlmclnt_unlock_ops);
+ if (status < 0)
+ goto out;
+
+ if (resp->status == nlm_granted)
+ goto out;
+
+ if (resp->status != nlm_lck_denied_nolocks)
+ printk("lockd: unexpected unlock status: %d\n",
+ ntohl(resp->status));
+ /* What to do now? I'm out of my depth... */
+ status = -ENOLCK;
+out:
+ trace_nlmclnt_unlock(&req->a_args.lock,
+ (const struct sockaddr *)&req->a_host->h_addr,
+ req->a_host->h_addrlen, req->a_res.status);
+ nlmclnt_release_call(req);
+ return status;
+}
+
+static void nlmclnt_unlock_prepare(struct rpc_task *task, void *data)
+{
+ struct nlm_rqst *req = data;
+ const struct nlmclnt_operations *nlmclnt_ops = req->a_host->h_nlmclnt_ops;
+ bool defer_call = false;
+
+ if (nlmclnt_ops && nlmclnt_ops->nlmclnt_unlock_prepare)
+ defer_call = nlmclnt_ops->nlmclnt_unlock_prepare(task, req->a_callback_data);
+
+ if (!defer_call)
+ rpc_call_start(task);
+}
+
+static void nlmclnt_unlock_callback(struct rpc_task *task, void *data)
+{
+ struct nlm_rqst *req = data;
+ u32 status = ntohl(req->a_res.status);
+
+ if (RPC_SIGNALLED(task))
+ goto die;
+
+ if (task->tk_status < 0) {
+ dprintk("lockd: unlock failed (err = %d)\n", -task->tk_status);
+ switch (task->tk_status) {
+ case -EACCES:
+ case -EIO:
+ goto die;
+ default:
+ goto retry_rebind;
+ }
+ }
+ if (status == NLM_LCK_DENIED_GRACE_PERIOD) {
+ rpc_delay(task, NLMCLNT_GRACE_WAIT);
+ goto retry_unlock;
+ }
+ if (status != NLM_LCK_GRANTED)
+ printk(KERN_WARNING "lockd: unexpected unlock status: %d\n", status);
+die:
+ return;
+ retry_rebind:
+ nlm_rebind_host(req->a_host);
+ retry_unlock:
+ rpc_restart_call(task);
+}
+
+static const struct rpc_call_ops nlmclnt_unlock_ops = {
+ .rpc_call_prepare = nlmclnt_unlock_prepare,
+ .rpc_call_done = nlmclnt_unlock_callback,
+ .rpc_release = nlmclnt_rpc_release,
+};
+
+/*
+ * Cancel a blocked lock request.
+ * We always use an async RPC call for this in order not to hang a
+ * process that has been Ctrl-C'ed.
+ */
+static int nlmclnt_cancel(struct nlm_host *host, int block, struct file_lock *fl)
+{
+ struct nlm_rqst *req;
+ int status;
+
+ dprintk("lockd: blocking lock attempt was interrupted by a signal.\n"
+ " Attempting to cancel lock.\n");
+
+ req = nlm_alloc_call(host);
+ if (!req)
+ return -ENOMEM;
+ req->a_flags = RPC_TASK_ASYNC;
+
+ nlmclnt_setlockargs(req, fl);
+ req->a_args.block = block;
+
+ refcount_inc(&req->a_count);
+ status = nlmclnt_async_call(nfs_file_cred(fl->c.flc_file), req,
+ NLMPROC_CANCEL, &nlmclnt_cancel_ops);
+ if (status == 0 && req->a_res.status == nlm_lck_denied)
+ status = -ENOLCK;
+ nlmclnt_release_call(req);
+ return status;
+}
+
+static void nlmclnt_cancel_callback(struct rpc_task *task, void *data)
+{
+ struct nlm_rqst *req = data;
+ u32 status = ntohl(req->a_res.status);
+
+ if (RPC_SIGNALLED(task))
+ goto die;
+
+ if (task->tk_status < 0) {
+ dprintk("lockd: CANCEL call error %d, retrying.\n",
+ task->tk_status);
+ goto retry_cancel;
+ }
+
+ switch (status) {
+ case NLM_LCK_GRANTED:
+ case NLM_LCK_DENIED_GRACE_PERIOD:
+ case NLM_LCK_DENIED:
+ /* Everything's good */
+ break;
+ case NLM_LCK_DENIED_NOLOCKS:
+ dprintk("lockd: CANCEL failed (server has no locks)\n");
+ goto retry_cancel;
+ default:
+ printk(KERN_NOTICE "lockd: weird return %d for CANCEL call\n",
+ status);
+ }
+
+die:
+ return;
+
+retry_cancel:
+ /* Don't ever retry more than 3 times */
+ if (req->a_retries++ >= NLMCLNT_MAX_RETRIES)
+ goto die;
+ nlm_rebind_host(req->a_host);
+ rpc_restart_call(task);
+ rpc_delay(task, 30 * HZ);
+}
+
+static const struct rpc_call_ops nlmclnt_cancel_ops = {
+ .rpc_call_done = nlmclnt_cancel_callback,
+ .rpc_release = nlmclnt_rpc_release,
+};
+
+/*
+ * Convert an NLM status code to a generic kernel errno
+ */
+static int
+nlm_stat_to_errno(__be32 status)
+{
+ switch(ntohl(status)) {
+ case NLM_LCK_GRANTED:
+ return 0;
+ case NLM_LCK_DENIED:
+ return -EAGAIN;
+ case NLM_LCK_DENIED_NOLOCKS:
+ case NLM_LCK_DENIED_GRACE_PERIOD:
+ return -ENOLCK;
+ case NLM_LCK_BLOCKED:
+ printk(KERN_NOTICE "lockd: unexpected status NLM_BLOCKED\n");
+ return -ENOLCK;
+#ifdef CONFIG_LOCKD_V4
+ case NLM_DEADLCK:
+ return -EDEADLK;
+ case NLM_ROFS:
+ return -EROFS;
+ case NLM_STALE_FH:
+ return -ESTALE;
+ case NLM_FBIG:
+ return -EOVERFLOW;
+ case NLM_FAILED:
+ return -ENOLCK;
+#endif
+ }
+ printk(KERN_NOTICE "lockd: unexpected server status %d\n",
+ ntohl(status));
+ return -ENOLCK;
+}
diff --git a/fs/lockd/clntxdr.c b/fs/lockd/clntxdr.c
new file mode 100644
index 000000000..3789ecb0b
--- /dev/null
+++ b/fs/lockd/clntxdr.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/clntxdr.c
+ *
+ * XDR functions to encode/decode NLM version 1 and 3 RPC
+ * arguments and results. NLM version 2 is not specified
+ * by a standard, thus it is not implemented.
+ *
+ * NLM client-side only.
+ *
+ * Copyright (C) 2010, Oracle. All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/stats.h>
+
+#include "lockd.h"
+
+#include <uapi/linux/nfs2.h>
+
+#define NLMDBG_FACILITY NLMDBG_XDR
+
+#if (NLMCLNT_OHSIZE > XDR_MAX_NETOBJ)
+# error "NLM host name cannot be larger than XDR_MAX_NETOBJ!"
+#endif
+
+/*
+ * Declare the space requirements for NLM arguments and replies as
+ * number of 32bit-words
+ */
+#define NLM_cookie_sz (1+(NLM_MAXCOOKIELEN>>2))
+#define NLM_caller_sz (1+(NLMCLNT_OHSIZE>>2))
+#define NLM_owner_sz (1+(NLMCLNT_OHSIZE>>2))
+#define NLM_fhandle_sz (1+(NFS2_FHSIZE>>2))
+#define NLM_lock_sz (3+NLM_caller_sz+NLM_owner_sz+NLM_fhandle_sz)
+#define NLM_holder_sz (4+NLM_owner_sz)
+
+#define NLM_testargs_sz (NLM_cookie_sz+1+NLM_lock_sz)
+#define NLM_lockargs_sz (NLM_cookie_sz+4+NLM_lock_sz)
+#define NLM_cancargs_sz (NLM_cookie_sz+2+NLM_lock_sz)
+#define NLM_unlockargs_sz (NLM_cookie_sz+NLM_lock_sz)
+
+#define NLM_testres_sz (NLM_cookie_sz+1+NLM_holder_sz)
+#define NLM_res_sz (NLM_cookie_sz+1)
+#define NLM_norep_sz (0)
+
+
+static s32 loff_t_to_s32(loff_t offset)
+{
+ s32 res;
+
+ if (offset >= NLM_OFFSET_MAX)
+ res = NLM_OFFSET_MAX;
+ else if (offset <= -NLM_OFFSET_MAX)
+ res = -NLM_OFFSET_MAX;
+ else
+ res = offset;
+ return res;
+}
+
+static void nlm_compute_offsets(const struct lockd_lock *lock,
+ u32 *l_offset, u32 *l_len)
+{
+ const struct file_lock *fl = &lock->fl;
+
+ *l_offset = loff_t_to_s32(fl->fl_start);
+ if (fl->fl_end == OFFSET_MAX)
+ *l_len = 0;
+ else
+ *l_len = loff_t_to_s32(fl->fl_end - fl->fl_start + 1);
+}
+
+/*
+ * Encode/decode NLMv3 basic data types
+ *
+ * Basic NLMv3 data types are not defined in an IETF standards
+ * document. X/Open has a description of these data types that
+ * is useful. See Chapter 10 of "Protocols for Interworking:
+ * XNFS, Version 3W".
+ *
+ * Not all basic data types have their own encoding and decoding
+ * functions. For run-time efficiency, some data types are encoded
+ * or decoded inline.
+ */
+
+static void encode_bool(struct xdr_stream *xdr, const int value)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4);
+ *p = value ? xdr_one : xdr_zero;
+}
+
+static void encode_int32(struct xdr_stream *xdr, const s32 value)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4);
+ *p = cpu_to_be32(value);
+}
+
+/*
+ * typedef opaque netobj<MAXNETOBJ_SZ>
+ */
+static void encode_netobj(struct xdr_stream *xdr,
+ const u8 *data, const unsigned int length)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4 + length);
+ xdr_encode_opaque(p, data, length);
+}
+
+static int decode_netobj(struct xdr_stream *xdr,
+ struct xdr_netobj *obj)
+{
+ ssize_t ret;
+
+ ret = xdr_stream_decode_opaque_inline(xdr, (void *)&obj->data,
+ XDR_MAX_NETOBJ);
+ if (unlikely(ret < 0))
+ return -EIO;
+ obj->len = ret;
+ return 0;
+}
+
+/*
+ * netobj cookie;
+ */
+static void encode_cookie(struct xdr_stream *xdr,
+ const struct lockd_cookie *cookie)
+{
+ encode_netobj(xdr, (u8 *)&cookie->data, cookie->len);
+}
+
+static int decode_cookie(struct xdr_stream *xdr,
+ struct lockd_cookie *cookie)
+{
+ u32 length;
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ length = be32_to_cpup(p++);
+ /* apparently HPUX can return empty cookies */
+ if (length == 0)
+ goto out_hpux;
+ if (length > NLM_MAXCOOKIELEN)
+ goto out_size;
+ p = xdr_inline_decode(xdr, length);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ cookie->len = length;
+ memcpy(cookie->data, p, length);
+ return 0;
+out_hpux:
+ cookie->len = 4;
+ memset(cookie->data, 0, 4);
+ return 0;
+out_size:
+ dprintk("NFS: returned cookie was too long: %u\n", length);
+ return -EIO;
+out_overflow:
+ return -EIO;
+}
+
+/*
+ * netobj fh;
+ */
+static void encode_fh(struct xdr_stream *xdr, const struct nfs_fh *fh)
+{
+ encode_netobj(xdr, (u8 *)&fh->data, NFS2_FHSIZE);
+}
+
+/*
+ * enum nlm_stats {
+ * LCK_GRANTED = 0,
+ * LCK_DENIED = 1,
+ * LCK_DENIED_NOLOCKS = 2,
+ * LCK_BLOCKED = 3,
+ * LCK_DENIED_GRACE_PERIOD = 4
+ * };
+ *
+ *
+ * struct nlm_stat {
+ * nlm_stats stat;
+ * };
+ *
+ * NB: we don't swap bytes for the NLM status values. The upper
+ * layers deal directly with the status value in network byte
+ * order.
+ */
+
+static void encode_nlm_stat(struct xdr_stream *xdr,
+ const __be32 stat)
+{
+ __be32 *p;
+
+ WARN_ON_ONCE(be32_to_cpu(stat) > NLM_LCK_DENIED_GRACE_PERIOD);
+ p = xdr_reserve_space(xdr, 4);
+ *p = stat;
+}
+
+static int decode_nlm_stat(struct xdr_stream *xdr,
+ __be32 *stat)
+{
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ if (unlikely(ntohl(*p) > ntohl(nlm_lck_denied_grace_period)))
+ goto out_enum;
+ *stat = *p;
+ return 0;
+out_enum:
+ dprintk("%s: server returned invalid nlm_stats value: %u\n",
+ __func__, be32_to_cpup(p));
+ return -EIO;
+out_overflow:
+ return -EIO;
+}
+
+/*
+ * struct nlm_holder {
+ * bool exclusive;
+ * int uppid;
+ * netobj oh;
+ * unsigned l_offset;
+ * unsigned l_len;
+ * };
+ */
+static void encode_nlm_holder(struct xdr_stream *xdr,
+ const struct lockd_res *result)
+{
+ const struct lockd_lock *lock = &result->lock;
+ u32 l_offset, l_len;
+ __be32 *p;
+
+ encode_bool(xdr, lock->fl.c.flc_type == F_RDLCK);
+ encode_int32(xdr, lock->svid);
+ encode_netobj(xdr, lock->oh.data, lock->oh.len);
+
+ p = xdr_reserve_space(xdr, 4 + 4);
+ nlm_compute_offsets(lock, &l_offset, &l_len);
+ *p++ = cpu_to_be32(l_offset);
+ *p = cpu_to_be32(l_len);
+}
+
+static int decode_nlm_holder(struct xdr_stream *xdr, struct lockd_res *result)
+{
+ struct lockd_lock *lock = &result->lock;
+ struct file_lock *fl = &lock->fl;
+ u32 exclusive, l_offset, l_len;
+ int error;
+ __be32 *p;
+ s32 end;
+
+ memset(lock, 0, sizeof(*lock));
+ locks_init_lock(fl);
+
+ p = xdr_inline_decode(xdr, 4 + 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+ exclusive = be32_to_cpup(p++);
+ lock->svid = be32_to_cpup(p);
+ fl->c.flc_pid = (pid_t)lock->svid;
+
+ error = decode_netobj(xdr, &lock->oh);
+ if (unlikely(error))
+ goto out;
+
+ p = xdr_inline_decode(xdr, 4 + 4);
+ if (unlikely(p == NULL))
+ goto out_overflow;
+
+ fl->c.flc_flags = FL_POSIX;
+ fl->c.flc_type = exclusive != 0 ? F_WRLCK : F_RDLCK;
+ l_offset = be32_to_cpup(p++);
+ l_len = be32_to_cpup(p);
+ end = l_offset + l_len - 1;
+
+ fl->fl_start = (loff_t)l_offset;
+ if (l_len == 0 || end < 0)
+ fl->fl_end = OFFSET_MAX;
+ else
+ fl->fl_end = (loff_t)end;
+ error = 0;
+out:
+ return error;
+out_overflow:
+ return -EIO;
+}
+
+/*
+ * string caller_name<LM_MAXSTRLEN>;
+ */
+static void encode_caller_name(struct xdr_stream *xdr, const char *name)
+{
+ /* NB: client-side does not set lock->len */
+ u32 length = strlen(name);
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4 + length);
+ xdr_encode_opaque(p, name, length);
+}
+
+/*
+ * struct nlm_lock {
+ * string caller_name<LM_MAXSTRLEN>;
+ * netobj fh;
+ * netobj oh;
+ * int uppid;
+ * unsigned l_offset;
+ * unsigned l_len;
+ * };
+ */
+static void encode_nlm_lock(struct xdr_stream *xdr,
+ const struct lockd_lock *lock)
+{
+ u32 l_offset, l_len;
+ __be32 *p;
+
+ encode_caller_name(xdr, lock->caller);
+ encode_fh(xdr, &lock->fh);
+ encode_netobj(xdr, lock->oh.data, lock->oh.len);
+
+ p = xdr_reserve_space(xdr, 4 + 4 + 4);
+ *p++ = cpu_to_be32(lock->svid);
+
+ nlm_compute_offsets(lock, &l_offset, &l_len);
+ *p++ = cpu_to_be32(l_offset);
+ *p = cpu_to_be32(l_len);
+}
+
+
+/*
+ * NLMv3 XDR encode functions
+ *
+ * NLMv3 argument types are defined in Chapter 10 of The Open Group's
+ * "Protocols for Interworking: XNFS, Version 3W".
+ */
+
+/*
+ * struct nlm_testargs {
+ * netobj cookie;
+ * bool exclusive;
+ * struct nlm_lock alock;
+ * };
+ */
+static void nlm_xdr_enc_testargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK);
+ encode_nlm_lock(xdr, lock);
+}
+
+/*
+ * struct nlm_lockargs {
+ * netobj cookie;
+ * bool block;
+ * bool exclusive;
+ * struct nlm_lock alock;
+ * bool reclaim;
+ * int state;
+ * };
+ */
+static void nlm_xdr_enc_lockargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_bool(xdr, args->block);
+ encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK);
+ encode_nlm_lock(xdr, lock);
+ encode_bool(xdr, args->reclaim);
+ encode_int32(xdr, args->state);
+}
+
+/*
+ * struct nlm_cancargs {
+ * netobj cookie;
+ * bool block;
+ * bool exclusive;
+ * struct nlm_lock alock;
+ * };
+ */
+static void nlm_xdr_enc_cancargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_bool(xdr, args->block);
+ encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK);
+ encode_nlm_lock(xdr, lock);
+}
+
+/*
+ * struct nlm_unlockargs {
+ * netobj cookie;
+ * struct nlm_lock alock;
+ * };
+ */
+static void nlm_xdr_enc_unlockargs(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_args *args = data;
+ const struct lockd_lock *lock = &args->lock;
+
+ encode_cookie(xdr, &args->cookie);
+ encode_nlm_lock(xdr, lock);
+}
+
+/*
+ * struct nlm_res {
+ * netobj cookie;
+ * nlm_stat stat;
+ * };
+ */
+static void nlm_xdr_enc_res(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_res *result = data;
+
+ encode_cookie(xdr, &result->cookie);
+ encode_nlm_stat(xdr, result->status);
+}
+
+/*
+ * union nlm_testrply switch (nlm_stats stat) {
+ * case LCK_DENIED:
+ * struct nlm_holder holder;
+ * default:
+ * void;
+ * };
+ *
+ * struct nlm_testres {
+ * netobj cookie;
+ * nlm_testrply test_stat;
+ * };
+ */
+static void encode_nlm_testrply(struct xdr_stream *xdr,
+ const struct lockd_res *result)
+{
+ if (result->status == nlm_lck_denied)
+ encode_nlm_holder(xdr, result);
+}
+
+static void nlm_xdr_enc_testres(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ const void *data)
+{
+ const struct lockd_res *result = data;
+
+ encode_cookie(xdr, &result->cookie);
+ encode_nlm_stat(xdr, result->status);
+ encode_nlm_testrply(xdr, result);
+}
+
+
+/*
+ * NLMv3 XDR decode functions
+ *
+ * NLMv3 result types are defined in Chapter 10 of The Open Group's
+ * "Protocols for Interworking: XNFS, Version 3W".
+ */
+
+/*
+ * union nlm_testrply switch (nlm_stats stat) {
+ * case LCK_DENIED:
+ * struct nlm_holder holder;
+ * default:
+ * void;
+ * };
+ *
+ * struct nlm_testres {
+ * netobj cookie;
+ * nlm_testrply test_stat;
+ * };
+ */
+static int decode_nlm_testrply(struct xdr_stream *xdr,
+ struct lockd_res *result)
+{
+ int error;
+
+ error = decode_nlm_stat(xdr, &result->status);
+ if (unlikely(error))
+ goto out;
+ if (result->status == nlm_lck_denied)
+ error = decode_nlm_holder(xdr, result);
+out:
+ return error;
+}
+
+static int nlm_xdr_dec_testres(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ void *data)
+{
+ struct lockd_res *result = data;
+ int error;
+
+ error = decode_cookie(xdr, &result->cookie);
+ if (unlikely(error))
+ goto out;
+ error = decode_nlm_testrply(xdr, result);
+out:
+ return error;
+}
+
+/*
+ * struct nlm_res {
+ * netobj cookie;
+ * nlm_stat stat;
+ * };
+ */
+static int nlm_xdr_dec_res(struct rpc_rqst *req,
+ struct xdr_stream *xdr,
+ void *data)
+{
+ struct lockd_res *result = data;
+ int error;
+
+ error = decode_cookie(xdr, &result->cookie);
+ if (unlikely(error))
+ goto out;
+ error = decode_nlm_stat(xdr, &result->status);
+out:
+ return error;
+}
+
+
+/*
+ * For NLM, a void procedure really returns nothing
+ */
+#define nlm_xdr_dec_norep NULL
+
+#define PROC(proc, argtype, restype) \
+[NLMPROC_##proc] = { \
+ .p_proc = NLMPROC_##proc, \
+ .p_encode = nlm_xdr_enc_##argtype, \
+ .p_decode = nlm_xdr_dec_##restype, \
+ .p_arglen = NLM_##argtype##_sz, \
+ .p_replen = NLM_##restype##_sz, \
+ .p_statidx = NLMPROC_##proc, \
+ .p_name = #proc, \
+ }
+
+static const struct rpc_procinfo nlm_procedures[] = {
+ PROC(TEST, testargs, testres),
+ PROC(LOCK, lockargs, res),
+ PROC(CANCEL, cancargs, res),
+ PROC(UNLOCK, unlockargs, res),
+ PROC(GRANTED, testargs, res),
+ PROC(TEST_MSG, testargs, norep),
+ PROC(LOCK_MSG, lockargs, norep),
+ PROC(CANCEL_MSG, cancargs, norep),
+ PROC(UNLOCK_MSG, unlockargs, norep),
+ PROC(GRANTED_MSG, testargs, norep),
+ PROC(TEST_RES, testres, norep),
+ PROC(LOCK_RES, res, norep),
+ PROC(CANCEL_RES, res, norep),
+ PROC(UNLOCK_RES, res, norep),
+ PROC(GRANTED_RES, res, norep),
+};
+
+static unsigned int nlm_version1_counts[ARRAY_SIZE(nlm_procedures)];
+static const struct rpc_version nlm_version1 = {
+ .number = 1,
+ .nrprocs = ARRAY_SIZE(nlm_procedures),
+ .procs = nlm_procedures,
+ .counts = nlm_version1_counts,
+};
+
+static unsigned int nlm_version3_counts[ARRAY_SIZE(nlm_procedures)];
+static const struct rpc_version nlm_version3 = {
+ .number = 3,
+ .nrprocs = ARRAY_SIZE(nlm_procedures),
+ .procs = nlm_procedures,
+ .counts = nlm_version3_counts,
+};
+
+static const struct rpc_version *nlm_versions[] = {
+ [1] = &nlm_version1,
+ [3] = &nlm_version3,
+#ifdef CONFIG_LOCKD_V4
+ [4] = &nlm_version4,
+#endif
+};
+
+static struct rpc_stat nlm_rpc_stats;
+
+const struct rpc_program nlm_program = {
+ .name = "lockd",
+ .number = NLM_PROGRAM,
+ .nrvers = ARRAY_SIZE(nlm_versions),
+ .version = nlm_versions,
+ .stats = &nlm_rpc_stats,
+};
diff --git a/fs/lockd/host.c b/fs/lockd/host.c
new file mode 100644
index 000000000..d572cb275
--- /dev/null
+++ b/fs/lockd/host.c
@@ -0,0 +1,723 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/host.c
+ *
+ * Management for NLM peer hosts. The nlm_host struct is shared
+ * between client and server implementation. The only reason to
+ * do so is to reduce code bloat.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/addr.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/mutex.h>
+
+#include <linux/sunrpc/svc_xprt.h>
+
+#include <net/ipv6.h>
+
+#include "lockd.h"
+#include "netns.h"
+
+#define NLMDBG_FACILITY NLMDBG_HOSTCACHE
+#define NLM_HOST_NRHASH 32
+#define NLM_HOST_REBIND (60 * HZ)
+#define NLM_HOST_EXPIRE (300 * HZ)
+#define NLM_HOST_COLLECT (120 * HZ)
+
+static struct hlist_head nlm_server_hosts[NLM_HOST_NRHASH];
+static struct hlist_head nlm_client_hosts[NLM_HOST_NRHASH];
+
+#define for_each_host(host, chain, table) \
+ for ((chain) = (table); \
+ (chain) < (table) + NLM_HOST_NRHASH; ++(chain)) \
+ hlist_for_each_entry((host), (chain), h_hash)
+
+#define for_each_host_safe(host, next, chain, table) \
+ for ((chain) = (table); \
+ (chain) < (table) + NLM_HOST_NRHASH; ++(chain)) \
+ hlist_for_each_entry_safe((host), (next), \
+ (chain), h_hash)
+
+static unsigned long nrhosts;
+static DEFINE_MUTEX(nlm_host_mutex);
+
+static void nlm_gc_hosts(struct net *net);
+
+struct nlm_lookup_host_info {
+ const int server; /* search for server|client */
+ const struct sockaddr *sap; /* address to search for */
+ const size_t salen; /* it's length */
+ const unsigned short protocol; /* transport to search for*/
+ const u32 version; /* NLM version to search for */
+ const char *hostname; /* remote's hostname */
+ const size_t hostname_len; /* it's length */
+ const int noresvport; /* use non-priv port */
+ struct net *net; /* network namespace to bind */
+ const struct cred *cred;
+};
+
+/*
+ * Hash function must work well on big- and little-endian platforms
+ */
+static unsigned int __nlm_hash32(const __be32 n)
+{
+ unsigned int hash = (__force u32)n ^ ((__force u32)n >> 16);
+ return hash ^ (hash >> 8);
+}
+
+static unsigned int __nlm_hash_addr4(const struct sockaddr *sap)
+{
+ const struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+ return __nlm_hash32(sin->sin_addr.s_addr);
+}
+
+static unsigned int __nlm_hash_addr6(const struct sockaddr *sap)
+{
+ const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+ const struct in6_addr addr = sin6->sin6_addr;
+ return __nlm_hash32(addr.s6_addr32[0]) ^
+ __nlm_hash32(addr.s6_addr32[1]) ^
+ __nlm_hash32(addr.s6_addr32[2]) ^
+ __nlm_hash32(addr.s6_addr32[3]);
+}
+
+static unsigned int nlm_hash_address(const struct sockaddr *sap)
+{
+ unsigned int hash;
+
+ switch (sap->sa_family) {
+ case AF_INET:
+ hash = __nlm_hash_addr4(sap);
+ break;
+ case AF_INET6:
+ hash = __nlm_hash_addr6(sap);
+ break;
+ default:
+ hash = 0;
+ }
+ return hash & (NLM_HOST_NRHASH - 1);
+}
+
+/*
+ * Allocate and initialize an nlm_host. Common to both client and server.
+ */
+static struct nlm_host *nlm_alloc_host(struct nlm_lookup_host_info *ni,
+ struct nsm_handle *nsm)
+{
+ struct nlm_host *host = NULL;
+ unsigned long now = jiffies;
+
+ if (nsm != NULL)
+ refcount_inc(&nsm->sm_count);
+ else {
+ nsm = nsm_get_handle(ni->net, ni->sap, ni->salen,
+ ni->hostname, ni->hostname_len);
+ if (unlikely(nsm == NULL)) {
+ dprintk("lockd: %s failed; no nsm handle\n",
+ __func__);
+ goto out;
+ }
+ }
+
+ host = kmalloc_obj(*host);
+ if (unlikely(host == NULL)) {
+ dprintk("lockd: %s failed; no memory\n", __func__);
+ nsm_release(nsm);
+ goto out;
+ }
+
+ memcpy(nlm_addr(host), ni->sap, ni->salen);
+ host->h_addrlen = ni->salen;
+ rpc_set_port(nlm_addr(host), 0);
+ host->h_srcaddrlen = 0;
+
+ host->h_rpcclnt = NULL;
+ host->h_name = nsm->sm_name;
+ host->h_version = ni->version;
+ host->h_proto = ni->protocol;
+ host->h_reclaiming = 0;
+ host->h_server = ni->server;
+ host->h_noresvport = ni->noresvport;
+ host->h_inuse = 0;
+ init_waitqueue_head(&host->h_gracewait);
+ init_rwsem(&host->h_rwsem);
+ host->h_state = 0;
+ host->h_nsmstate = 0;
+ host->h_pidcount = 0;
+ refcount_set(&host->h_count, 1);
+ mutex_init(&host->h_mutex);
+ host->h_nextrebind = now + NLM_HOST_REBIND;
+ host->h_expires = now + NLM_HOST_EXPIRE;
+ INIT_LIST_HEAD(&host->h_lockowners);
+ spin_lock_init(&host->h_lock);
+ INIT_LIST_HEAD(&host->h_granted);
+ INIT_LIST_HEAD(&host->h_reclaim);
+ host->h_nsmhandle = nsm;
+ host->h_addrbuf = nsm->sm_addrbuf;
+ host->net = ni->net;
+ host->h_cred = get_cred(ni->cred);
+ strscpy(host->nodename, utsname()->nodename, sizeof(host->nodename));
+
+out:
+ return host;
+}
+
+/*
+ * Destroy an nlm_host and free associated resources
+ *
+ * Caller must hold nlm_host_mutex.
+ */
+static void nlm_destroy_host_locked(struct nlm_host *host)
+{
+ struct rpc_clnt *clnt;
+ struct lockd_net *ln = net_generic(host->net, lockd_net_id);
+
+ dprintk("lockd: destroy host %s\n", host->h_name);
+
+ hlist_del_init(&host->h_hash);
+
+ nsm_unmonitor(host);
+ nsm_release(host->h_nsmhandle);
+
+ clnt = host->h_rpcclnt;
+ if (clnt != NULL)
+ rpc_shutdown_client(clnt);
+ put_cred(host->h_cred);
+ kfree(host);
+
+ ln->nrhosts--;
+ nrhosts--;
+}
+
+/**
+ * nlmclnt_lookup_host - Find an NLM host handle matching a remote server
+ * @sap: network address of server
+ * @salen: length of server address
+ * @protocol: transport protocol to use
+ * @version: NLM protocol version
+ * @hostname: '\0'-terminated hostname of server
+ * @noresvport: 1 if non-privileged port should be used
+ * @net: pointer to net namespace
+ * @cred: pointer to cred
+ *
+ * Returns an nlm_host structure that matches the passed-in
+ * [server address, transport protocol, NLM version, server hostname].
+ * If one doesn't already exist in the host cache, a new handle is
+ * created and returned.
+ */
+struct nlm_host *nlmclnt_lookup_host(const struct sockaddr *sap,
+ const size_t salen,
+ const unsigned short protocol,
+ const u32 version,
+ const char *hostname,
+ int noresvport,
+ struct net *net,
+ const struct cred *cred)
+{
+ struct nlm_lookup_host_info ni = {
+ .server = 0,
+ .sap = sap,
+ .salen = salen,
+ .protocol = protocol,
+ .version = version,
+ .hostname = hostname,
+ .hostname_len = strlen(hostname),
+ .noresvport = noresvport,
+ .net = net,
+ .cred = cred,
+ };
+ struct hlist_head *chain;
+ struct nlm_host *host;
+ struct nsm_handle *nsm = NULL;
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ dprintk("lockd: %s(host='%s', vers=%u, proto=%s)\n", __func__,
+ (hostname ? hostname : "<none>"), version,
+ (protocol == IPPROTO_UDP ? "udp" : "tcp"));
+
+ mutex_lock(&nlm_host_mutex);
+
+ chain = &nlm_client_hosts[nlm_hash_address(sap)];
+ hlist_for_each_entry(host, chain, h_hash) {
+ if (host->net != net)
+ continue;
+ if (!rpc_cmp_addr(nlm_addr(host), sap))
+ continue;
+
+ /* Same address. Share an NSM handle if we already have one */
+ if (nsm == NULL)
+ nsm = host->h_nsmhandle;
+
+ if (host->h_proto != protocol)
+ continue;
+ if (host->h_version != version)
+ continue;
+
+ nlm_get_host(host);
+ dprintk("lockd: %s found host %s (%s)\n", __func__,
+ host->h_name, host->h_addrbuf);
+ goto out;
+ }
+
+ host = nlm_alloc_host(&ni, nsm);
+ if (unlikely(host == NULL))
+ goto out;
+
+ hlist_add_head(&host->h_hash, chain);
+ ln->nrhosts++;
+ nrhosts++;
+
+ dprintk("lockd: %s created host %s (%s)\n", __func__,
+ host->h_name, host->h_addrbuf);
+
+out:
+ mutex_unlock(&nlm_host_mutex);
+ return host;
+}
+
+/**
+ * nlmclnt_release_host - release client nlm_host
+ * @host: nlm_host to release
+ *
+ */
+void nlmclnt_release_host(struct nlm_host *host)
+{
+ if (host == NULL)
+ return;
+
+ dprintk("lockd: release client host %s\n", host->h_name);
+
+ WARN_ON_ONCE(host->h_server);
+
+ if (refcount_dec_and_mutex_lock(&host->h_count, &nlm_host_mutex)) {
+ WARN_ON_ONCE(!list_empty(&host->h_lockowners));
+ WARN_ON_ONCE(!list_empty(&host->h_granted));
+ WARN_ON_ONCE(!list_empty(&host->h_reclaim));
+
+ nlm_destroy_host_locked(host);
+ mutex_unlock(&nlm_host_mutex);
+ }
+}
+
+/* Callback for rpc_cancel_tasks() - matches all tasks for cancellation */
+static bool nlmclnt_match_all(const struct rpc_task *task, const void *data)
+{
+ return true;
+}
+
+/**
+ * nlmclnt_shutdown_rpc_clnt - safely shut down NLM client RPC operations
+ * @host: nlm_host to shut down
+ *
+ * Cancels outstanding RPC tasks and marks the client as shut down.
+ * Synchronizes with nlmclnt_release_host() via nlm_host_mutex to prevent
+ * races between shutdown and host destruction. Safe to call if h_rpcclnt
+ * is NULL or already shut down.
+ */
+void nlmclnt_shutdown_rpc_clnt(struct nlm_host *host)
+{
+ struct rpc_clnt *clnt;
+
+ mutex_lock(&nlm_host_mutex);
+ clnt = host->h_rpcclnt;
+ if (clnt) {
+ clnt->cl_shutdown = 1;
+ rpc_cancel_tasks(clnt, -EIO, nlmclnt_match_all, NULL);
+ }
+ mutex_unlock(&nlm_host_mutex);
+}
+EXPORT_SYMBOL_GPL(nlmclnt_shutdown_rpc_clnt);
+
+/**
+ * nlmsvc_lookup_host - Find an NLM host handle matching a remote client
+ * @rqstp: incoming NLM request
+ * @hostname: name of client host
+ * @hostname_len: length of client hostname
+ *
+ * Returns an nlm_host structure that matches the [client address,
+ * transport protocol, NLM version, client hostname] of the passed-in
+ * NLM request. If one doesn't already exist in the host cache, a
+ * new handle is created and returned.
+ *
+ * Before possibly creating a new nlm_host, construct a sockaddr
+ * for a specific source address in case the local system has
+ * multiple network addresses. The family of the address in
+ * rq_daddr is guaranteed to be the same as the family of the
+ * address in rq_addr, so it's safe to use the same family for
+ * the source address.
+ */
+struct nlm_host *nlmsvc_lookup_host(const struct svc_rqst *rqstp,
+ const char *hostname,
+ const size_t hostname_len)
+{
+ struct hlist_head *chain;
+ struct nlm_host *host = NULL;
+ struct nsm_handle *nsm = NULL;
+ struct sockaddr *src_sap = svc_daddr(rqstp);
+ size_t src_len = rqstp->rq_daddrlen;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_lookup_host_info ni = {
+ .server = 1,
+ .sap = svc_addr(rqstp),
+ .salen = rqstp->rq_addrlen,
+ .protocol = rqstp->rq_prot,
+ .version = rqstp->rq_vers,
+ .hostname = hostname,
+ .hostname_len = hostname_len,
+ .net = net,
+ };
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ dprintk("lockd: %s(host='%.*s', vers=%u, proto=%s)\n", __func__,
+ (int)hostname_len, hostname, rqstp->rq_vers,
+ (rqstp->rq_prot == IPPROTO_UDP ? "udp" : "tcp"));
+
+ mutex_lock(&nlm_host_mutex);
+
+ if (time_after_eq(jiffies, ln->next_gc))
+ nlm_gc_hosts(net);
+
+ chain = &nlm_server_hosts[nlm_hash_address(ni.sap)];
+ hlist_for_each_entry(host, chain, h_hash) {
+ if (host->net != net)
+ continue;
+ if (!rpc_cmp_addr(nlm_addr(host), ni.sap))
+ continue;
+
+ /* Same address. Share an NSM handle if we already have one */
+ if (nsm == NULL)
+ nsm = host->h_nsmhandle;
+
+ if (host->h_proto != ni.protocol)
+ continue;
+ if (host->h_version != ni.version)
+ continue;
+ if (!rpc_cmp_addr(nlm_srcaddr(host), src_sap))
+ continue;
+
+ /* Move to head of hash chain. */
+ hlist_del(&host->h_hash);
+ hlist_add_head(&host->h_hash, chain);
+
+ nlm_get_host(host);
+ dprintk("lockd: %s found host %s (%s)\n",
+ __func__, host->h_name, host->h_addrbuf);
+ goto out;
+ }
+
+ host = nlm_alloc_host(&ni, nsm);
+ if (unlikely(host == NULL))
+ goto out;
+
+ memcpy(nlm_srcaddr(host), src_sap, src_len);
+ host->h_srcaddrlen = src_len;
+ hlist_add_head(&host->h_hash, chain);
+ ln->nrhosts++;
+ nrhosts++;
+
+ refcount_inc(&host->h_count);
+
+ dprintk("lockd: %s created host %s (%s)\n",
+ __func__, host->h_name, host->h_addrbuf);
+
+out:
+ mutex_unlock(&nlm_host_mutex);
+ return host;
+}
+
+/**
+ * nlmsvc_release_host - release server nlm_host
+ * @host: nlm_host to release
+ *
+ * Host is destroyed later in nlm_gc_host().
+ */
+void nlmsvc_release_host(struct nlm_host *host)
+{
+ if (host == NULL)
+ return;
+
+ dprintk("lockd: release server host %s\n", host->h_name);
+
+ WARN_ON_ONCE(!host->h_server);
+ refcount_dec(&host->h_count);
+}
+
+/*
+ * Create the NLM RPC client for an NLM peer
+ */
+struct rpc_clnt *
+nlm_bind_host(struct nlm_host *host)
+{
+ struct rpc_clnt *clnt;
+
+ dprintk("lockd: nlm_bind_host %s (%s)\n",
+ host->h_name, host->h_addrbuf);
+
+ /* Lock host handle */
+ mutex_lock(&host->h_mutex);
+
+ /* If we've already created an RPC client, check whether
+ * RPC rebind is required
+ */
+ if ((clnt = host->h_rpcclnt) != NULL) {
+ nlm_rebind_host(host);
+ } else {
+ unsigned long increment = nlm_timeout * HZ;
+ struct rpc_timeout timeparms = {
+ .to_initval = increment,
+ .to_increment = increment,
+ .to_maxval = increment * 6UL,
+ .to_retries = 5U,
+ };
+ struct rpc_create_args args = {
+ .net = host->net,
+ .protocol = host->h_proto,
+ .address = nlm_addr(host),
+ .addrsize = host->h_addrlen,
+ .timeout = &timeparms,
+ .servername = host->h_name,
+ .program = &nlm_program,
+ .version = host->h_version,
+ .authflavor = RPC_AUTH_UNIX,
+ .flags = (RPC_CLNT_CREATE_NOPING |
+ RPC_CLNT_CREATE_AUTOBIND |
+ RPC_CLNT_CREATE_REUSEPORT),
+ .cred = host->h_cred,
+ };
+
+ /*
+ * lockd retries server side blocks automatically so we want
+ * those to be soft RPC calls. Client side calls need to be
+ * hard RPC tasks.
+ */
+ if (!host->h_server)
+ args.flags |= RPC_CLNT_CREATE_HARDRTRY;
+ if (host->h_noresvport)
+ args.flags |= RPC_CLNT_CREATE_NONPRIVPORT;
+ if (host->h_srcaddrlen)
+ args.saddress = nlm_srcaddr(host);
+
+ clnt = rpc_create(&args);
+ if (!IS_ERR(clnt))
+ host->h_rpcclnt = clnt;
+ else {
+ printk("lockd: couldn't create RPC handle for %s\n", host->h_name);
+ clnt = NULL;
+ }
+ }
+
+ mutex_unlock(&host->h_mutex);
+ return clnt;
+}
+
+/**
+ * nlm_rebind_host - If needed, force a portmap lookup of the peer's lockd port
+ * @host: NLM host handle for peer
+ *
+ * This is not needed when using a connection-oriented protocol, such as TCP.
+ * The existing autobind mechanism is sufficient to force a rebind when
+ * required, e.g. on connection state transitions.
+ */
+void
+nlm_rebind_host(struct nlm_host *host)
+{
+ if (host->h_proto != IPPROTO_UDP)
+ return;
+
+ if (host->h_rpcclnt && time_after_eq(jiffies, host->h_nextrebind)) {
+ rpc_force_rebind(host->h_rpcclnt);
+ host->h_nextrebind = jiffies + NLM_HOST_REBIND;
+ }
+}
+
+/*
+ * Increment NLM host count
+ */
+struct nlm_host * nlm_get_host(struct nlm_host *host)
+{
+ if (host) {
+ dprintk("lockd: get host %s\n", host->h_name);
+ refcount_inc(&host->h_count);
+ host->h_expires = jiffies + NLM_HOST_EXPIRE;
+ }
+ return host;
+}
+
+static struct nlm_host *next_host_state(struct hlist_head *cache,
+ struct nsm_handle *nsm,
+ const struct lockd_reboot *info)
+{
+ struct nlm_host *host;
+ struct hlist_head *chain;
+
+ mutex_lock(&nlm_host_mutex);
+ for_each_host(host, chain, cache) {
+ if (host->h_nsmhandle == nsm
+ && host->h_nsmstate != info->state) {
+ host->h_nsmstate = info->state;
+ host->h_state++;
+
+ nlm_get_host(host);
+ mutex_unlock(&nlm_host_mutex);
+ return host;
+ }
+ }
+
+ mutex_unlock(&nlm_host_mutex);
+ return NULL;
+}
+
+/**
+ * nlm_host_rebooted - Release all resources held by rebooted host
+ * @net: network namespace
+ * @info: pointer to decoded results of NLM_SM_NOTIFY call
+ *
+ * We were notified that the specified host has rebooted. Release
+ * all resources held by that peer.
+ */
+void nlm_host_rebooted(const struct net *net, const struct lockd_reboot *info)
+{
+ struct nsm_handle *nsm;
+ struct nlm_host *host;
+
+ nsm = nsm_reboot_lookup(net, info);
+ if (unlikely(nsm == NULL))
+ return;
+
+ /* Mark all hosts tied to this NSM state as having rebooted.
+ * We run the loop repeatedly, because we drop the host table
+ * lock for this.
+ * To avoid processing a host several times, we match the nsmstate.
+ */
+ while ((host = next_host_state(nlm_server_hosts, nsm, info)) != NULL) {
+ nlmsvc_free_host_resources(host);
+ nlmsvc_release_host(host);
+ }
+ while ((host = next_host_state(nlm_client_hosts, nsm, info)) != NULL) {
+ nlmclnt_recovery(host);
+ nlmclnt_release_host(host);
+ }
+
+ nsm_release(nsm);
+}
+
+static void nlm_complain_hosts(struct net *net)
+{
+ struct hlist_head *chain;
+ struct nlm_host *host;
+
+ if (net) {
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ if (ln->nrhosts == 0)
+ return;
+ pr_warn("lockd: couldn't shutdown host module for net %x!\n",
+ net->ns.inum);
+ dprintk("lockd: %lu hosts left in net %x:\n", ln->nrhosts,
+ net->ns.inum);
+ } else {
+ if (nrhosts == 0)
+ return;
+ printk(KERN_WARNING "lockd: couldn't shutdown host module!\n");
+ dprintk("lockd: %lu hosts left:\n", nrhosts);
+ }
+
+ for_each_host(host, chain, nlm_server_hosts) {
+ if (net && host->net != net)
+ continue;
+ dprintk(" %s (cnt %d use %d exp %ld net %x)\n",
+ host->h_name, refcount_read(&host->h_count),
+ host->h_inuse, host->h_expires, host->net->ns.inum);
+ }
+}
+
+void
+nlm_shutdown_hosts_net(struct net *net)
+{
+ struct hlist_head *chain;
+ struct nlm_host *host;
+
+ mutex_lock(&nlm_host_mutex);
+
+ /* First, make all hosts eligible for gc */
+ dprintk("lockd: nuking all hosts in net %x...\n",
+ net ? net->ns.inum : 0);
+ for_each_host(host, chain, nlm_server_hosts) {
+ if (net && host->net != net)
+ continue;
+ host->h_expires = jiffies - 1;
+ if (host->h_rpcclnt) {
+ rpc_shutdown_client(host->h_rpcclnt);
+ host->h_rpcclnt = NULL;
+ }
+ nlmsvc_free_host_resources(host);
+ }
+
+ /* Then, perform a garbage collection pass */
+ nlm_gc_hosts(net);
+ nlm_complain_hosts(net);
+ mutex_unlock(&nlm_host_mutex);
+}
+
+/*
+ * Shut down the hosts module.
+ * Note that this routine is called only at server shutdown time.
+ */
+void
+nlm_shutdown_hosts(void)
+{
+ dprintk("lockd: shutting down host module\n");
+ nlm_shutdown_hosts_net(NULL);
+}
+
+/*
+ * Garbage collect any unused NLM hosts.
+ * This GC combines reference counting for async operations with
+ * mark & sweep for resources held by remote clients.
+ */
+static void
+nlm_gc_hosts(struct net *net)
+{
+ struct hlist_head *chain;
+ struct hlist_node *next;
+ struct nlm_host *host;
+
+ dprintk("lockd: host garbage collection for net %x\n",
+ net ? net->ns.inum : 0);
+ for_each_host(host, chain, nlm_server_hosts) {
+ if (net && host->net != net)
+ continue;
+ host->h_inuse = 0;
+ }
+
+ /* Mark all hosts that hold locks, blocks or shares */
+ nlmsvc_mark_resources(net);
+
+ for_each_host_safe(host, next, chain, nlm_server_hosts) {
+ if (net && host->net != net)
+ continue;
+ if (host->h_inuse || time_before(jiffies, host->h_expires)) {
+ dprintk("nlm_gc_hosts skipping %s "
+ "(cnt %d use %d exp %ld net %x)\n",
+ host->h_name, refcount_read(&host->h_count),
+ host->h_inuse, host->h_expires,
+ host->net->ns.inum);
+ continue;
+ }
+ if (refcount_dec_if_one(&host->h_count))
+ nlm_destroy_host_locked(host);
+ }
+
+ if (net) {
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ ln->next_gc = jiffies + NLM_HOST_COLLECT;
+ }
+}
diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h
new file mode 100644
index 000000000..14cc952fe
--- /dev/null
+++ b/fs/lockd/lockd.h
@@ -0,0 +1,483 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#ifndef _LOCKD_LOCKD_H
+#define _LOCKD_LOCKD_H
+
+#include <linux/exportfs.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <net/ipv6.h>
+#include <linux/fs.h>
+#include <linux/kref.h>
+#include <linux/refcount.h>
+#include <linux/utsname.h>
+#include "nlm.h"
+#include <linux/lockd/bind.h>
+#include "xdr.h"
+#include <linux/sunrpc/debug.h>
+#include <linux/sunrpc/svc.h>
+
+/*
+ * Enable lockd debugging.
+ * Requires CONFIG_SUNRPC_DEBUG.
+ */
+#undef ifdebug
+#if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
+# define ifdebug(flag) if (unlikely(nlm_debug & NLMDBG_##flag))
+#else
+# define ifdebug(flag) if (0)
+#endif
+
+#define NLMDBG_SVC 0x0001
+#define NLMDBG_CLIENT 0x0002
+#define NLMDBG_CLNTLOCK 0x0004
+#define NLMDBG_SVCLOCK 0x0008
+#define NLMDBG_MONITOR 0x0010
+#define NLMDBG_CLNTSUBS 0x0020
+#define NLMDBG_SVCSUBS 0x0040
+#define NLMDBG_HOSTCACHE 0x0080
+#define NLMDBG_XDR 0x0100
+#define NLMDBG_ALL 0x7fff
+
+/*
+ * Version string
+ */
+#define LOCKD_VERSION "0.5"
+
+/*
+ * Default timeout for RPC calls (seconds)
+ */
+#define LOCKD_DFLT_TIMEO 10
+
+/*
+ * Number of leading bytes of nfs_fh.data that file_hash()
+ * digests when bucketing nlm_files[]. Sized for historical
+ * NFSv2 handles; nfs_fh.data must be initialized at least
+ * this far before lookup, regardless of fh.size.
+ */
+#define LOCKD_FH_HASH_SIZE 32
+
+/* error codes new to NLMv4 */
+#define nlm4_deadlock cpu_to_be32(NLM_DEADLCK)
+#define nlm4_rofs cpu_to_be32(NLM_ROFS)
+#define nlm4_stale_fh cpu_to_be32(NLM_STALE_FH)
+#define nlm4_fbig cpu_to_be32(NLM_FBIG)
+#define nlm4_failed cpu_to_be32(NLM_FAILED)
+
+/*
+ * Internal-use status codes, not to be placed on the wire.
+ * Version handlers translate these to appropriate wire values.
+ */
+#define nlm__int__drop_reply cpu_to_be32(30000)
+#define nlm__int__deadlock cpu_to_be32(30001)
+#define nlm__int__stale_fh cpu_to_be32(30002)
+#define nlm__int__failed cpu_to_be32(30003)
+
+/*
+ * Lockd host handle (used both by the client and server personality).
+ */
+struct nlm_host {
+ struct hlist_node h_hash; /* doubly linked list */
+ struct sockaddr_storage h_addr; /* peer address */
+ size_t h_addrlen;
+ struct sockaddr_storage h_srcaddr; /* our address (optional) */
+ size_t h_srcaddrlen;
+ struct rpc_clnt *h_rpcclnt; /* RPC client to talk to peer */
+ char *h_name; /* remote hostname */
+ u32 h_version; /* interface version */
+ unsigned short h_proto; /* transport proto */
+ unsigned short h_reclaiming : 1,
+ h_server : 1, /* server side, not client side */
+ h_noresvport : 1,
+ h_inuse : 1;
+ wait_queue_head_t h_gracewait; /* wait while reclaiming */
+ struct rw_semaphore h_rwsem; /* Reboot recovery lock */
+ u32 h_state; /* pseudo-state counter */
+ u32 h_nsmstate; /* true remote NSM state */
+ u32 h_pidcount; /* Pseudopids */
+ refcount_t h_count; /* reference count */
+ struct mutex h_mutex; /* mutex for pmap binding */
+ unsigned long h_nextrebind; /* next portmap call */
+ unsigned long h_expires; /* eligible for GC */
+ struct list_head h_lockowners; /* Lockowners for the client */
+ spinlock_t h_lock;
+ struct list_head h_granted; /* Locks in GRANTED state */
+ struct list_head h_reclaim; /* Locks in RECLAIM state */
+ struct nsm_handle *h_nsmhandle; /* NSM status handle */
+ char *h_addrbuf; /* address eyecatcher */
+ struct net *net; /* host net */
+ const struct cred *h_cred;
+ char nodename[UNX_MAXNODENAME + 1];
+ const struct nlmclnt_operations *h_nlmclnt_ops; /* Callback ops for NLM users */
+};
+
+/*
+ * The largest string sm_addrbuf should hold is a full-size IPv6 address
+ * (no "::" anywhere) with a scope ID. The buffer size is computed to
+ * hold eight groups of colon-separated four-hex-digit numbers, a
+ * percent sign, a scope id (at most 32 bits, in decimal), and NUL.
+ */
+#define NSM_ADDRBUF ((8 * 4 + 7) + (1 + 10) + 1)
+
+struct nsm_handle {
+ struct list_head sm_link;
+ refcount_t sm_count;
+ char *sm_mon_name;
+ char *sm_name;
+ struct sockaddr_storage sm_addr;
+ size_t sm_addrlen;
+ unsigned int sm_monitored : 1,
+ sm_sticky : 1; /* don't unmonitor */
+ struct nsm_private sm_priv;
+ char sm_addrbuf[NSM_ADDRBUF];
+};
+
+/*
+ * Rigorous type checking on sockaddr type conversions
+ */
+static inline struct sockaddr *nlm_addr(const struct nlm_host *host)
+{
+ return (struct sockaddr *)&host->h_addr;
+}
+
+static inline struct sockaddr *nlm_srcaddr(const struct nlm_host *host)
+{
+ return (struct sockaddr *)&host->h_srcaddr;
+}
+
+/*
+ * Map an fl_owner_t into a unique 32-bit "pid"
+ */
+struct nlm_lockowner {
+ struct list_head list;
+ refcount_t count;
+
+ struct nlm_host *host;
+ fl_owner_t owner;
+ uint32_t pid;
+};
+
+/*
+ * This is the representation of a blocked client lock.
+ */
+struct nlm_wait {
+ struct list_head b_list; /* linked list */
+ wait_queue_head_t b_wait; /* where to wait on */
+ struct nlm_host *b_host;
+ struct file_lock *b_lock; /* local file lock */
+ __be32 b_status; /* grant callback status */
+};
+
+/*
+ * Memory chunk for NLM client RPC request.
+ */
+#define NLMCLNT_OHSIZE ((__NEW_UTS_LEN) + 10u)
+struct nlm_rqst {
+ refcount_t a_count;
+ unsigned int a_flags; /* initial RPC task flags */
+ struct nlm_host * a_host; /* host handle */
+ struct lockd_args a_args; /* arguments */
+ struct lockd_res a_res; /* result */
+ struct nlm_block * a_block;
+ unsigned int a_retries; /* Retry count */
+ u8 a_owner[NLMCLNT_OHSIZE];
+ void * a_callback_data; /* sent to nlmclnt_operations callbacks */
+};
+
+struct lockd_share;
+
+/*
+ * This struct describes a file held open by lockd on behalf of
+ * an NFS client.
+ */
+struct nlm_file {
+ struct hlist_node f_list; /* linked list */
+ struct nfs_fh f_handle; /* NFS file handle */
+ struct file * f_file[2]; /* VFS file pointers,
+ indexed by O_ flags */
+ struct lockd_share * f_shares; /* DOS shares */
+ struct list_head f_blocks; /* blocked locks */
+ unsigned int f_locks; /* guesstimate # of locks */
+ unsigned int f_count; /* reference count */
+ struct mutex f_mutex; /* avoid concurrent access */
+};
+
+/*
+ * This is a server block (i.e. a lock requested by some client which
+ * couldn't be granted because of a conflicting lock).
+ */
+#define NLM_NEVER (~(unsigned long) 0)
+/* timeout on non-blocking call: */
+#define NLM_TIMEOUT (7 * HZ)
+
+struct nlm_block {
+ struct kref b_count; /* Reference count */
+ struct list_head b_list; /* linked list of all blocks */
+ struct list_head b_flist; /* linked list (per file) */
+ struct nlm_rqst * b_call; /* RPC args & callback info */
+ struct svc_serv * b_daemon; /* NLM service */
+ struct nlm_host * b_host; /* host handle for RPC clnt */
+ unsigned long b_when; /* next re-xmit */
+ unsigned int b_id; /* block id */
+ unsigned char b_granted; /* VFS granted lock */
+ struct nlm_file * b_file; /* file in question */
+ struct cache_req * b_cache_req; /* deferred request handling */
+ struct cache_deferred_req * b_deferred_req;
+ unsigned int b_flags; /* block flags */
+#define B_QUEUED 1 /* lock queued */
+#define B_GOT_CALLBACK 2 /* got lock or conflicting lock */
+#define B_TIMED_OUT 4 /* filesystem too slow to respond */
+};
+
+/*
+ * Global variables
+ */
+extern const struct rpc_program nlm_program;
+extern const struct svc_version nlmsvc_version1;
+extern const struct svc_version nlmsvc_version3;
+#ifdef CONFIG_LOCKD_V4
+extern const struct svc_version nlmsvc_version4;
+#endif
+extern int nlmsvc_grace_period;
+extern unsigned long nlm_timeout;
+extern bool nsm_use_hostnames;
+extern u32 nsm_local_state;
+
+extern struct timer_list nlmsvc_retry;
+
+/*
+ * Lockd client functions
+ */
+struct nlm_rqst * nlm_alloc_call(struct nlm_host *host);
+int nlm_async_call(struct nlm_rqst *, u32, const struct rpc_call_ops *);
+int nlm_async_reply(struct nlm_rqst *, u32, const struct rpc_call_ops *);
+void nlmclnt_release_call(struct nlm_rqst *);
+void nlmclnt_prepare_block(struct nlm_wait *block, struct nlm_host *host,
+ struct file_lock *fl);
+void nlmclnt_queue_block(struct nlm_wait *block);
+__be32 nlmclnt_dequeue_block(struct nlm_wait *block);
+int nlmclnt_wait(struct nlm_wait *block, struct nlm_rqst *req, long timeout);
+__be32 nlmclnt_grant(const struct sockaddr *addr,
+ const struct lockd_lock *lock);
+void nlmclnt_recovery(struct nlm_host *);
+int nlmclnt_reclaim(struct nlm_host *, struct file_lock *,
+ struct nlm_rqst *);
+void nlmclnt_next_cookie(struct lockd_cookie *);
+
+#ifdef CONFIG_LOCKD_V4
+extern const struct rpc_version nlm_version4;
+#endif
+
+/*
+ * Host cache
+ */
+struct nlm_host *nlmclnt_lookup_host(const struct sockaddr *sap,
+ const size_t salen,
+ const unsigned short protocol,
+ const u32 version,
+ const char *hostname,
+ int noresvport,
+ struct net *net,
+ const struct cred *cred);
+void nlmclnt_release_host(struct nlm_host *);
+struct nlm_host *nlmsvc_lookup_host(const struct svc_rqst *rqstp,
+ const char *hostname,
+ const size_t hostname_len);
+void nlmsvc_release_host(struct nlm_host *);
+struct rpc_clnt * nlm_bind_host(struct nlm_host *);
+void nlm_rebind_host(struct nlm_host *);
+struct nlm_host * nlm_get_host(struct nlm_host *);
+void nlm_shutdown_hosts(void);
+void nlm_shutdown_hosts_net(struct net *net);
+void nlm_host_rebooted(const struct net *net,
+ const struct lockd_reboot *);
+
+/*
+ * Host monitoring
+ */
+int nsm_monitor(const struct nlm_host *host);
+void nsm_unmonitor(const struct nlm_host *host);
+
+struct nsm_handle *nsm_get_handle(const struct net *net,
+ const struct sockaddr *sap,
+ const size_t salen,
+ const char *hostname,
+ const size_t hostname_len);
+struct nsm_handle *nsm_reboot_lookup(const struct net *net,
+ const struct lockd_reboot *info);
+void nsm_release(struct nsm_handle *nsm);
+
+/*
+ * This is used in garbage collection and resource reclaim
+ * A return value != 0 means destroy the lock/block/share
+ */
+typedef int (*nlm_host_match_fn_t)(void *owner, struct nlm_host *ref);
+
+/*
+ * Server-side lock handling
+ */
+int lock_to_openmode(struct file_lock *);
+__be32 nlmsvc_lock(struct svc_rqst *, struct nlm_file *,
+ struct nlm_host *, struct lockd_lock *, int,
+ struct lockd_cookie *, int);
+__be32 nlmsvc_unlock(struct net *net, struct nlm_file *, struct lockd_lock *);
+__be32 nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
+ struct nlm_host *host, struct lockd_lock *lock,
+ struct lockd_lock *conflock);
+__be32 nlmsvc_cancel_blocked(struct net *net, struct nlm_file *, struct lockd_lock *);
+void nlmsvc_retry_blocked(struct svc_rqst *rqstp);
+void nlmsvc_traverse_blocks(struct nlm_host *, struct nlm_file *,
+ nlm_host_match_fn_t match);
+void nlmsvc_grant_reply(struct lockd_cookie *, __be32);
+void nlmsvc_release_call(struct nlm_rqst *);
+void nlmsvc_locks_init_private(struct file_lock *, struct nlm_host *, pid_t);
+int nlmsvc_dispatch(struct svc_rqst *rqstp);
+
+/*
+ * File handling for the server personality
+ */
+__be32 nlm_lookup_file(struct svc_rqst *, struct nlm_file **,
+ struct lockd_lock *, int);
+void nlm_release_file(struct nlm_file *);
+void nlmsvc_put_lockowner(struct nlm_lockowner *);
+void nlmsvc_release_lockowner(struct lockd_lock *);
+void nlmsvc_mark_resources(struct net *);
+void nlmsvc_free_host_resources(struct nlm_host *);
+void nlmsvc_invalidate_all(void);
+
+static inline struct file *nlmsvc_file_file(const struct nlm_file *file)
+{
+ return file->f_file[O_RDONLY] ?
+ file->f_file[O_RDONLY] : file->f_file[O_WRONLY];
+}
+
+static inline struct inode *nlmsvc_file_inode(struct nlm_file *file)
+{
+ return file_inode(nlmsvc_file_file(file));
+}
+
+static inline bool
+nlmsvc_file_cannot_lock(const struct nlm_file *file)
+{
+ return exportfs_cannot_lock(nlmsvc_file_file(file)->f_path.dentry->d_sb->s_export_op);
+}
+
+static inline int __nlm_privileged_request4(const struct sockaddr *sap)
+{
+ const struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+
+ if (ntohs(sin->sin_port) > 1023)
+ return 0;
+
+ return ipv4_is_loopback(sin->sin_addr.s_addr);
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static inline int __nlm_privileged_request6(const struct sockaddr *sap)
+{
+ const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+
+ if (ntohs(sin6->sin6_port) > 1023)
+ return 0;
+
+ if (ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_MAPPED)
+ return ipv4_is_loopback(sin6->sin6_addr.s6_addr32[3]);
+
+ return ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LOOPBACK;
+}
+#else /* IS_ENABLED(CONFIG_IPV6) */
+static inline int __nlm_privileged_request6(const struct sockaddr *sap)
+{
+ return 0;
+}
+#endif /* IS_ENABLED(CONFIG_IPV6) */
+
+/*
+ * Ensure incoming requests are from local privileged callers.
+ *
+ * Return TRUE if sender is local and is connecting via a privileged port;
+ * otherwise return FALSE.
+ */
+static inline int nlm_privileged_requester(const struct svc_rqst *rqstp)
+{
+ const struct sockaddr *sap = svc_addr(rqstp);
+
+ switch (sap->sa_family) {
+ case AF_INET:
+ return __nlm_privileged_request4(sap);
+ case AF_INET6:
+ return __nlm_privileged_request6(sap);
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Compare two NLM locks.
+ * When the second lock is of type F_UNLCK, this acts like a wildcard.
+ */
+static inline int nlm_compare_locks(const struct file_lock *fl1,
+ const struct file_lock *fl2)
+{
+ return file_inode(fl1->c.flc_file) == file_inode(fl2->c.flc_file)
+ && fl1->c.flc_pid == fl2->c.flc_pid
+ && fl1->c.flc_owner == fl2->c.flc_owner
+ && fl1->fl_start == fl2->fl_start
+ && fl1->fl_end == fl2->fl_end
+ &&(fl1->c.flc_type == fl2->c.flc_type || fl2->c.flc_type == F_UNLCK);
+}
+
+/**
+ * lockd_set_file_lock_range3 - set the byte range of a file_lock
+ * @fl: file_lock whose length fields are to be initialized
+ * @off: starting offset of the lock, in bytes
+ * @len: length of the byte range, in bytes, or zero
+ *
+ * NLMv3 uses a (start, length) representation for lock byte ranges,
+ * while the kernel's file_lock uses (start, end). Treat a length of
+ * zero or arithmetic overflow (end wrapping negative when the sum
+ * exceeds S32_MAX) as "lock to end of file."
+ */
+static inline void
+lockd_set_file_lock_range3(struct file_lock *fl, u32 off, u32 len)
+{
+ s32 end = off + len - 1;
+
+ fl->fl_start = off;
+ if (len == 0 || end < 0)
+ fl->fl_end = OFFSET_MAX;
+ else
+ fl->fl_end = end;
+}
+
+/**
+ * lockd_set_file_lock_range4 - set the byte range of a file_lock
+ * @fl: file_lock whose length fields are to be initialized
+ * @off: starting offset of the lock, in bytes
+ * @len: length of the byte range, in bytes, or zero
+ *
+ * The NLMv4 protocol represents lock byte ranges as (start, length),
+ * where length zero means "lock to end of file." The kernel's file_lock
+ * structure uses (start, end) representation. Convert from NLMv4 format
+ * to file_lock format, clamping the starting offset and treating
+ * arithmetic overflow as "lock to EOF."
+ */
+static inline void
+lockd_set_file_lock_range4(struct file_lock *fl, u64 off, u64 len)
+{
+ u64 clamped_off = (off > OFFSET_MAX) ? OFFSET_MAX : off;
+ s64 end = clamped_off + len - 1;
+
+ fl->fl_start = clamped_off;
+ if (len == 0 || end < 0)
+ fl->fl_end = OFFSET_MAX;
+ else
+ fl->fl_end = end;
+}
+
+extern const struct lock_manager_operations nlmsvc_lock_operations;
+
+#endif /* _LOCKD_LOCKD_H */
diff --git a/fs/lockd/mon.c b/fs/lockd/mon.c
new file mode 100644
index 000000000..a8f5ac6f0
--- /dev/null
+++ b/fs/lockd/mon.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/mon.c
+ *
+ * The kernel statd client.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/slab.h>
+
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/addr.h>
+#include <linux/sunrpc/xprtsock.h>
+#include <linux/sunrpc/svc.h>
+
+#include <linux/unaligned.h>
+
+#include "lockd.h"
+#include "netns.h"
+
+#define NLMDBG_FACILITY NLMDBG_MONITOR
+#define NSM_PROGRAM 100024
+#define NSM_VERSION 1
+
+enum {
+ NSMPROC_NULL,
+ NSMPROC_STAT,
+ NSMPROC_MON,
+ NSMPROC_UNMON,
+ NSMPROC_UNMON_ALL,
+ NSMPROC_SIMU_CRASH,
+ NSMPROC_NOTIFY,
+};
+
+struct nsm_args {
+ struct nsm_private *priv;
+ u32 prog; /* RPC callback info */
+ u32 vers;
+ u32 proc;
+
+ char *mon_name;
+ const char *nodename;
+};
+
+struct nsm_res {
+ u32 status;
+ u32 state;
+};
+
+static const struct rpc_program nsm_program;
+static DEFINE_SPINLOCK(nsm_lock);
+
+/*
+ * Local NSM state
+ */
+u32 __read_mostly nsm_local_state;
+bool __read_mostly nsm_use_hostnames;
+
+static inline struct sockaddr *nsm_addr(const struct nsm_handle *nsm)
+{
+ return (struct sockaddr *)&nsm->sm_addr;
+}
+
+static struct rpc_clnt *nsm_create(struct net *net, const char *nodename)
+{
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ struct rpc_create_args args = {
+ .net = net,
+ .protocol = XPRT_TRANSPORT_TCP,
+ .address = (struct sockaddr *)&sin,
+ .addrsize = sizeof(sin),
+ .servername = "rpc.statd",
+ .nodename = nodename,
+ .program = &nsm_program,
+ .version = NSM_VERSION,
+ .authflavor = RPC_AUTH_NULL,
+ .flags = RPC_CLNT_CREATE_NOPING,
+ .cred = current_cred(),
+ };
+
+ return rpc_create(&args);
+}
+
+static int nsm_mon_unmon(struct nsm_handle *nsm, u32 proc, struct nsm_res *res,
+ const struct nlm_host *host)
+{
+ int status;
+ struct rpc_clnt *clnt;
+ struct nsm_args args = {
+ .priv = &nsm->sm_priv,
+ .prog = NLM_PROGRAM,
+ .vers = 3,
+ .proc = NLMPROC_NSM_NOTIFY,
+ .mon_name = nsm->sm_mon_name,
+ .nodename = host->nodename,
+ };
+ struct rpc_message msg = {
+ .rpc_argp = &args,
+ .rpc_resp = res,
+ };
+
+ memset(res, 0, sizeof(*res));
+
+ clnt = nsm_create(host->net, host->nodename);
+ if (IS_ERR(clnt)) {
+ dprintk("lockd: failed to create NSM upcall transport, "
+ "status=%ld, net=%x\n", PTR_ERR(clnt),
+ host->net->ns.inum);
+ return PTR_ERR(clnt);
+ }
+
+ msg.rpc_proc = &clnt->cl_procinfo[proc];
+ status = rpc_call_sync(clnt, &msg, RPC_TASK_SOFTCONN);
+ if (status == -ECONNREFUSED) {
+ dprintk("lockd: NSM upcall RPC failed, status=%d, forcing rebind\n",
+ status);
+ rpc_force_rebind(clnt);
+ status = rpc_call_sync(clnt, &msg, RPC_TASK_SOFTCONN);
+ }
+ if (status < 0)
+ dprintk("lockd: NSM upcall RPC failed, status=%d\n",
+ status);
+ else
+ status = 0;
+
+ rpc_shutdown_client(clnt);
+ return status;
+}
+
+/**
+ * nsm_monitor - Notify a peer in case we reboot
+ * @host: pointer to nlm_host of peer to notify
+ *
+ * If this peer is not already monitored, this function sends an
+ * upcall to the local rpc.statd to record the name/address of
+ * the peer to notify in case we reboot.
+ *
+ * Returns zero if the peer is monitored by the local rpc.statd;
+ * otherwise a negative errno value is returned.
+ */
+int nsm_monitor(const struct nlm_host *host)
+{
+ struct nsm_handle *nsm = host->h_nsmhandle;
+ struct nsm_res res;
+ int status;
+
+ dprintk("lockd: nsm_monitor(%s)\n", nsm->sm_name);
+
+ if (nsm->sm_monitored)
+ return 0;
+
+ /*
+ * Choose whether to record the caller_name or IP address of
+ * this peer in the local rpc.statd's database.
+ */
+ nsm->sm_mon_name = nsm_use_hostnames ? nsm->sm_name : nsm->sm_addrbuf;
+
+ status = nsm_mon_unmon(nsm, NSMPROC_MON, &res, host);
+ if (unlikely(res.status != 0))
+ status = -EIO;
+ if (unlikely(status < 0)) {
+ pr_notice_ratelimited("lockd: cannot monitor %s\n", nsm->sm_name);
+ return status;
+ }
+
+ nsm->sm_monitored = 1;
+ if (unlikely(nsm_local_state != res.state)) {
+ nsm_local_state = res.state;
+ dprintk("lockd: NSM state changed to %d\n", nsm_local_state);
+ }
+ return 0;
+}
+
+/**
+ * nsm_unmonitor - Unregister peer notification
+ * @host: pointer to nlm_host of peer to stop monitoring
+ *
+ * If this peer is monitored, this function sends an upcall to
+ * tell the local rpc.statd not to send this peer a notification
+ * when we reboot.
+ */
+void nsm_unmonitor(const struct nlm_host *host)
+{
+ struct nsm_handle *nsm = host->h_nsmhandle;
+ struct nsm_res res;
+ int status;
+
+ if (refcount_read(&nsm->sm_count) == 1
+ && nsm->sm_monitored && !nsm->sm_sticky) {
+ dprintk("lockd: nsm_unmonitor(%s)\n", nsm->sm_name);
+
+ status = nsm_mon_unmon(nsm, NSMPROC_UNMON, &res, host);
+ if (res.status != 0)
+ status = -EIO;
+ if (status < 0)
+ printk(KERN_NOTICE "lockd: cannot unmonitor %s\n",
+ nsm->sm_name);
+ else
+ nsm->sm_monitored = 0;
+ }
+}
+
+static struct nsm_handle *nsm_lookup_hostname(const struct list_head *nsm_handles,
+ const char *hostname, const size_t len)
+{
+ struct nsm_handle *nsm;
+
+ list_for_each_entry(nsm, nsm_handles, sm_link)
+ if (strlen(nsm->sm_name) == len &&
+ memcmp(nsm->sm_name, hostname, len) == 0)
+ return nsm;
+ return NULL;
+}
+
+static struct nsm_handle *nsm_lookup_addr(const struct list_head *nsm_handles,
+ const struct sockaddr *sap)
+{
+ struct nsm_handle *nsm;
+
+ list_for_each_entry(nsm, nsm_handles, sm_link)
+ if (rpc_cmp_addr(nsm_addr(nsm), sap))
+ return nsm;
+ return NULL;
+}
+
+static struct nsm_handle *nsm_lookup_priv(const struct list_head *nsm_handles,
+ const struct nsm_private *priv)
+{
+ struct nsm_handle *nsm;
+
+ list_for_each_entry(nsm, nsm_handles, sm_link)
+ if (memcmp(nsm->sm_priv.data, priv->data,
+ sizeof(priv->data)) == 0)
+ return nsm;
+ return NULL;
+}
+
+/*
+ * Construct a unique cookie to match this nsm_handle to this monitored
+ * host. It is passed to the local rpc.statd via NSMPROC_MON, and
+ * returned via NLMPROC_SM_NOTIFY, in the "priv" field of these
+ * requests.
+ *
+ * The NSM protocol requires that these cookies be unique while the
+ * system is running. We prefer a stronger requirement of making them
+ * unique across reboots. If user space bugs cause a stale cookie to
+ * be sent to the kernel, it could cause the wrong host to lose its
+ * lock state if cookies were not unique across reboots.
+ *
+ * The cookies are exposed only to local user space via loopback. They
+ * do not appear on the physical network. If we want greater security
+ * for some reason, nsm_init_private() could perform a one-way hash to
+ * obscure the contents of the cookie.
+ */
+static void nsm_init_private(struct nsm_handle *nsm)
+{
+ u64 *p = (u64 *)&nsm->sm_priv.data;
+ s64 ns;
+
+ ns = ktime_get_ns();
+ put_unaligned(ns, p);
+ put_unaligned((unsigned long)nsm, p + 1);
+}
+
+static struct nsm_handle *nsm_create_handle(const struct sockaddr *sap,
+ const size_t salen,
+ const char *hostname,
+ const size_t hostname_len)
+{
+ struct nsm_handle *new;
+
+ if (!hostname)
+ return NULL;
+
+ new = kzalloc(sizeof(*new) + hostname_len + 1, GFP_KERNEL);
+ if (unlikely(new == NULL))
+ return NULL;
+
+ refcount_set(&new->sm_count, 1);
+ new->sm_name = (char *)(new + 1);
+ memcpy(nsm_addr(new), sap, salen);
+ new->sm_addrlen = salen;
+ nsm_init_private(new);
+
+ if (rpc_ntop(nsm_addr(new), new->sm_addrbuf,
+ sizeof(new->sm_addrbuf)) == 0)
+ (void)snprintf(new->sm_addrbuf, sizeof(new->sm_addrbuf),
+ "unsupported address family");
+ memcpy(new->sm_name, hostname, hostname_len);
+ new->sm_name[hostname_len] = '\0';
+
+ return new;
+}
+
+/**
+ * nsm_get_handle - Find or create a cached nsm_handle
+ * @net: network namespace
+ * @sap: pointer to socket address of handle to find
+ * @salen: length of socket address
+ * @hostname: pointer to C string containing hostname to find
+ * @hostname_len: length of C string
+ *
+ * Behavior is modulated by the global nsm_use_hostnames variable.
+ *
+ * Returns a cached nsm_handle after bumping its ref count, or
+ * returns a fresh nsm_handle if a handle that matches @sap and/or
+ * @hostname cannot be found in the handle cache. Returns NULL if
+ * an error occurs.
+ */
+struct nsm_handle *nsm_get_handle(const struct net *net,
+ const struct sockaddr *sap,
+ const size_t salen, const char *hostname,
+ const size_t hostname_len)
+{
+ struct nsm_handle *cached, *new = NULL;
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ if (hostname && memchr(hostname, '/', hostname_len) != NULL) {
+ if (printk_ratelimit()) {
+ printk(KERN_WARNING "Invalid hostname \"%.*s\" "
+ "in NFS lock request\n",
+ (int)hostname_len, hostname);
+ }
+ return NULL;
+ }
+
+retry:
+ spin_lock(&nsm_lock);
+
+ if (nsm_use_hostnames && hostname != NULL)
+ cached = nsm_lookup_hostname(&ln->nsm_handles,
+ hostname, hostname_len);
+ else
+ cached = nsm_lookup_addr(&ln->nsm_handles, sap);
+
+ if (cached != NULL) {
+ refcount_inc(&cached->sm_count);
+ spin_unlock(&nsm_lock);
+ kfree(new);
+ dprintk("lockd: found nsm_handle for %s (%s), "
+ "cnt %d\n", cached->sm_name,
+ cached->sm_addrbuf,
+ refcount_read(&cached->sm_count));
+ return cached;
+ }
+
+ if (new != NULL) {
+ list_add(&new->sm_link, &ln->nsm_handles);
+ spin_unlock(&nsm_lock);
+ dprintk("lockd: created nsm_handle for %s (%s)\n",
+ new->sm_name, new->sm_addrbuf);
+ return new;
+ }
+
+ spin_unlock(&nsm_lock);
+
+ new = nsm_create_handle(sap, salen, hostname, hostname_len);
+ if (unlikely(new == NULL))
+ return NULL;
+ goto retry;
+}
+
+/**
+ * nsm_reboot_lookup - match NLMPROC_SM_NOTIFY arguments to an nsm_handle
+ * @net: network namespace
+ * @info: pointer to NLMPROC_SM_NOTIFY arguments
+ *
+ * Returns a matching nsm_handle if found in the nsm cache. The returned
+ * nsm_handle's reference count is bumped. Otherwise returns NULL if some
+ * error occurred.
+ */
+struct nsm_handle *nsm_reboot_lookup(const struct net *net,
+ const struct lockd_reboot *info)
+{
+ struct nsm_handle *cached;
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ spin_lock(&nsm_lock);
+
+ cached = nsm_lookup_priv(&ln->nsm_handles, &info->priv);
+ if (unlikely(cached == NULL)) {
+ spin_unlock(&nsm_lock);
+ dprintk("lockd: never saw rebooted peer '%.*s' before\n",
+ info->len, info->mon);
+ return cached;
+ }
+
+ refcount_inc(&cached->sm_count);
+ spin_unlock(&nsm_lock);
+
+ dprintk("lockd: host %s (%s) rebooted, cnt %d\n",
+ cached->sm_name, cached->sm_addrbuf,
+ refcount_read(&cached->sm_count));
+ return cached;
+}
+
+/**
+ * nsm_release - Release an NSM handle
+ * @nsm: pointer to handle to be released
+ *
+ */
+void nsm_release(struct nsm_handle *nsm)
+{
+ if (refcount_dec_and_lock(&nsm->sm_count, &nsm_lock)) {
+ list_del(&nsm->sm_link);
+ spin_unlock(&nsm_lock);
+ dprintk("lockd: destroyed nsm_handle for %s (%s)\n",
+ nsm->sm_name, nsm->sm_addrbuf);
+ kfree(nsm);
+ }
+}
+
+/*
+ * XDR functions for NSM.
+ *
+ * See https://www.opengroup.org/ for details on the Network
+ * Status Monitor wire protocol.
+ */
+
+static void encode_nsm_string(struct xdr_stream *xdr, const char *string)
+{
+ const u32 len = strlen(string);
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, 4 + len);
+ xdr_encode_opaque(p, string, len);
+}
+
+/*
+ * "mon_name" specifies the host to be monitored.
+ */
+static void encode_mon_name(struct xdr_stream *xdr, const struct nsm_args *argp)
+{
+ encode_nsm_string(xdr, argp->mon_name);
+}
+
+/*
+ * The "my_id" argument specifies the hostname and RPC procedure
+ * to be called when the status manager receives notification
+ * (via the NLMPROC_SM_NOTIFY call) that the state of host "mon_name"
+ * has changed.
+ */
+static void encode_my_id(struct xdr_stream *xdr, const struct nsm_args *argp)
+{
+ __be32 *p;
+
+ encode_nsm_string(xdr, argp->nodename);
+ p = xdr_reserve_space(xdr, 4 + 4 + 4);
+ *p++ = cpu_to_be32(argp->prog);
+ *p++ = cpu_to_be32(argp->vers);
+ *p = cpu_to_be32(argp->proc);
+}
+
+/*
+ * The "mon_id" argument specifies the non-private arguments
+ * of an NSMPROC_MON or NSMPROC_UNMON call.
+ */
+static void encode_mon_id(struct xdr_stream *xdr, const struct nsm_args *argp)
+{
+ encode_mon_name(xdr, argp);
+ encode_my_id(xdr, argp);
+}
+
+/*
+ * The "priv" argument may contain private information required
+ * by the NSMPROC_MON call. This information will be supplied in the
+ * NLMPROC_SM_NOTIFY call.
+ */
+static void encode_priv(struct xdr_stream *xdr, const struct nsm_args *argp)
+{
+ __be32 *p;
+
+ p = xdr_reserve_space(xdr, SM_PRIV_SIZE);
+ xdr_encode_opaque_fixed(p, argp->priv->data, SM_PRIV_SIZE);
+}
+
+static void nsm_xdr_enc_mon(struct rpc_rqst *req, struct xdr_stream *xdr,
+ const void *argp)
+{
+ encode_mon_id(xdr, argp);
+ encode_priv(xdr, argp);
+}
+
+static void nsm_xdr_enc_unmon(struct rpc_rqst *req, struct xdr_stream *xdr,
+ const void *argp)
+{
+ encode_mon_id(xdr, argp);
+}
+
+static int nsm_xdr_dec_stat_res(struct rpc_rqst *rqstp,
+ struct xdr_stream *xdr,
+ void *data)
+{
+ struct nsm_res *resp = data;
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4 + 4);
+ if (unlikely(p == NULL))
+ return -EIO;
+ resp->status = be32_to_cpup(p++);
+ resp->state = be32_to_cpup(p);
+
+ dprintk("lockd: %s status %d state %d\n",
+ __func__, resp->status, resp->state);
+ return 0;
+}
+
+static int nsm_xdr_dec_stat(struct rpc_rqst *rqstp,
+ struct xdr_stream *xdr,
+ void *data)
+{
+ struct nsm_res *resp = data;
+ __be32 *p;
+
+ p = xdr_inline_decode(xdr, 4);
+ if (unlikely(p == NULL))
+ return -EIO;
+ resp->state = be32_to_cpup(p);
+
+ dprintk("lockd: %s state %d\n", __func__, resp->state);
+ return 0;
+}
+
+#define SM_my_name_sz (1+XDR_QUADLEN(SM_MAXSTRLEN))
+#define SM_my_id_sz (SM_my_name_sz+3)
+#define SM_mon_name_sz (1+XDR_QUADLEN(SM_MAXSTRLEN))
+#define SM_mon_id_sz (SM_mon_name_sz+SM_my_id_sz)
+#define SM_priv_sz (XDR_QUADLEN(SM_PRIV_SIZE))
+#define SM_mon_sz (SM_mon_id_sz+SM_priv_sz)
+#define SM_monres_sz 2
+#define SM_unmonres_sz 1
+
+static const struct rpc_procinfo nsm_procedures[] = {
+[NSMPROC_MON] = {
+ .p_proc = NSMPROC_MON,
+ .p_encode = nsm_xdr_enc_mon,
+ .p_decode = nsm_xdr_dec_stat_res,
+ .p_arglen = SM_mon_sz,
+ .p_replen = SM_monres_sz,
+ .p_statidx = NSMPROC_MON,
+ .p_name = "MONITOR",
+ },
+[NSMPROC_UNMON] = {
+ .p_proc = NSMPROC_UNMON,
+ .p_encode = nsm_xdr_enc_unmon,
+ .p_decode = nsm_xdr_dec_stat,
+ .p_arglen = SM_mon_id_sz,
+ .p_replen = SM_unmonres_sz,
+ .p_statidx = NSMPROC_UNMON,
+ .p_name = "UNMONITOR",
+ },
+};
+
+static unsigned int nsm_version1_counts[ARRAY_SIZE(nsm_procedures)];
+static const struct rpc_version nsm_version1 = {
+ .number = 1,
+ .nrprocs = ARRAY_SIZE(nsm_procedures),
+ .procs = nsm_procedures,
+ .counts = nsm_version1_counts,
+};
+
+static const struct rpc_version *nsm_version[] = {
+ [1] = &nsm_version1,
+};
+
+static struct rpc_stat nsm_stats;
+
+static const struct rpc_program nsm_program = {
+ .name = "statd",
+ .number = NSM_PROGRAM,
+ .nrvers = ARRAY_SIZE(nsm_version),
+ .version = nsm_version,
+ .stats = &nsm_stats
+};
diff --git a/fs/lockd/netlink.c b/fs/lockd/netlink.c
new file mode 100644
index 000000000..880c42b4f
--- /dev/null
+++ b/fs/lockd/netlink.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/lockd.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "netlink.h"
+
+#include <uapi/linux/lockd_netlink.h>
+
+/* LOCKD_CMD_SERVER_SET - do */
+static const struct nla_policy lockd_server_set_nl_policy[LOCKD_A_SERVER_UDP_PORT + 1] = {
+ [LOCKD_A_SERVER_GRACETIME] = { .type = NLA_U32, },
+ [LOCKD_A_SERVER_TCP_PORT] = { .type = NLA_U16, },
+ [LOCKD_A_SERVER_UDP_PORT] = { .type = NLA_U16, },
+};
+
+/* Ops table for lockd */
+static const struct genl_split_ops lockd_nl_ops[] = {
+ {
+ .cmd = LOCKD_CMD_SERVER_SET,
+ .doit = lockd_nl_server_set_doit,
+ .policy = lockd_server_set_nl_policy,
+ .maxattr = LOCKD_A_SERVER_UDP_PORT,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = LOCKD_CMD_SERVER_GET,
+ .doit = lockd_nl_server_get_doit,
+ .flags = GENL_CMD_CAP_DO,
+ },
+};
+
+struct genl_family lockd_nl_family __ro_after_init = {
+ .name = LOCKD_FAMILY_NAME,
+ .version = LOCKD_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = lockd_nl_ops,
+ .n_split_ops = ARRAY_SIZE(lockd_nl_ops),
+};
diff --git a/fs/lockd/netlink.h b/fs/lockd/netlink.h
new file mode 100644
index 000000000..d8408f077
--- /dev/null
+++ b/fs/lockd/netlink.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/lockd.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_LOCKD_GEN_H
+#define _LINUX_LOCKD_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/lockd_netlink.h>
+
+int lockd_nl_server_set_doit(struct sk_buff *skb, struct genl_info *info);
+int lockd_nl_server_get_doit(struct sk_buff *skb, struct genl_info *info);
+
+extern struct genl_family lockd_nl_family;
+
+#endif /* _LINUX_LOCKD_GEN_H */
diff --git a/fs/lockd/netns.h b/fs/lockd/netns.h
new file mode 100644
index 000000000..88e8e2a97
--- /dev/null
+++ b/fs/lockd/netns.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LOCKD_NETNS_H__
+#define __LOCKD_NETNS_H__
+
+#include <linux/fs.h>
+#include <linux/filelock.h>
+#include <net/netns/generic.h>
+
+struct lockd_net {
+ unsigned int nlmsvc_users;
+ unsigned long next_gc;
+ unsigned long nrhosts;
+ u32 gracetime;
+ u16 tcp_port;
+ u16 udp_port;
+
+ struct delayed_work grace_period_end;
+ struct lock_manager lockd_manager;
+
+ struct list_head nsm_handles;
+};
+
+extern unsigned int lockd_net_id;
+
+#endif
diff --git a/fs/lockd/nlm.h b/fs/lockd/nlm.h
new file mode 100644
index 000000000..47be65d01
--- /dev/null
+++ b/fs/lockd/nlm.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Declarations for the Network Lock Manager protocol.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#ifndef _LOCKD_NLM_H
+#define _LOCKD_NLM_H
+
+
+/* Maximum file offset in file_lock.fl_end */
+# define NLM_OFFSET_MAX ((s32) 0x7fffffff)
+# define NLM4_OFFSET_MAX ((s64) ((~(u64)0) >> 1))
+
+/* Return states for NLM */
+enum {
+ NLM_LCK_GRANTED = 0,
+ NLM_LCK_DENIED = 1,
+ NLM_LCK_DENIED_NOLOCKS = 2,
+ NLM_LCK_BLOCKED = 3,
+ NLM_LCK_DENIED_GRACE_PERIOD = 4,
+#ifdef CONFIG_LOCKD_V4
+ NLM_DEADLCK = 5,
+ NLM_ROFS = 6,
+ NLM_STALE_FH = 7,
+ NLM_FBIG = 8,
+ NLM_FAILED = 9,
+#endif
+};
+
+#define NLM_PROGRAM 100021
+
+#define NLMPROC_NULL 0
+#define NLMPROC_TEST 1
+#define NLMPROC_LOCK 2
+#define NLMPROC_CANCEL 3
+#define NLMPROC_UNLOCK 4
+#define NLMPROC_GRANTED 5
+#define NLMPROC_TEST_MSG 6
+#define NLMPROC_LOCK_MSG 7
+#define NLMPROC_CANCEL_MSG 8
+#define NLMPROC_UNLOCK_MSG 9
+#define NLMPROC_GRANTED_MSG 10
+#define NLMPROC_TEST_RES 11
+#define NLMPROC_LOCK_RES 12
+#define NLMPROC_CANCEL_RES 13
+#define NLMPROC_UNLOCK_RES 14
+#define NLMPROC_GRANTED_RES 15
+#define NLMPROC_NSM_NOTIFY 16 /* statd callback */
+#define NLMPROC_SHARE 20
+#define NLMPROC_UNSHARE 21
+#define NLMPROC_NM_LOCK 22
+#define NLMPROC_FREE_ALL 23
+
+#endif /* _LOCKD_NLM_H */
diff --git a/fs/lockd/nlm3xdr_gen.c b/fs/lockd/nlm3xdr_gen.c
new file mode 100644
index 000000000..4b6e3ee7a
--- /dev/null
+++ b/fs/lockd/nlm3xdr_gen.c
@@ -0,0 +1,686 @@
+// SPDX-License-Identifier: GPL-2.0
+// Generated by xdrgen. Manual edits will be lost.
+// XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x
+// XDR specification modification time: Mon Jun 29 20:42:29 2026
+
+#include <linux/sunrpc/svc.h>
+
+#include "nlm3xdr_gen.h"
+
+static bool __maybe_unused
+xdrgen_decode_netobj(struct xdr_stream *xdr, netobj *ptr)
+{
+ return xdrgen_decode_opaque(xdr, ptr, MAXNETOBJ_SZ);
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_stats(struct xdr_stream *xdr, nlm_stats *ptr)
+{
+ __be32 raw;
+ u32 val;
+
+ if (xdr_stream_decode_be32(xdr, &raw) < 0)
+ return false;
+ val = be32_to_cpu(raw);
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case LCK_GRANTED:
+ case LCK_DENIED:
+ case LCK_DENIED_NOLOCKS:
+ case LCK_BLOCKED:
+ case LCK_DENIED_GRACE_PERIOD:
+ break;
+ default:
+ return false;
+ }
+ *ptr = raw;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_stat(struct xdr_stream *xdr, struct nlm_stat *ptr)
+{
+ if (!xdrgen_decode_nlm_stats(xdr, &ptr->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_res(struct xdr_stream *xdr, struct nlm_res *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm_stat(xdr, &ptr->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_holder(struct xdr_stream *xdr, struct nlm_holder *ptr)
+{
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_int(xdr, &ptr->uppid))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->oh))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_offset))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_testrply(struct xdr_stream *xdr, struct nlm_testrply *ptr)
+{
+ if (!xdrgen_decode_nlm_stats(xdr, &ptr->stat))
+ return false;
+ switch (ptr->stat) {
+ case __constant_cpu_to_be32(LCK_DENIED):
+ if (!xdrgen_decode_nlm_holder(xdr, &ptr->u.holder))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_testres(struct xdr_stream *xdr, struct nlm_testres *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm_testrply(xdr, &ptr->test_stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_lock(struct xdr_stream *xdr, struct nlm_lock *ptr)
+{
+ if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXSTRLEN))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->oh))
+ return false;
+ if (!xdrgen_decode_int(xdr, &ptr->uppid))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_offset))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_lockargs(struct xdr_stream *xdr, struct nlm_lockargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->block))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->reclaim))
+ return false;
+ if (!xdrgen_decode_int(xdr, &ptr->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_cancargs(struct xdr_stream *xdr, struct nlm_cancargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->block))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_testargs(struct xdr_stream *xdr, struct nlm_testargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_unlockargs(struct xdr_stream *xdr, struct nlm_unlockargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fsh_mode(struct xdr_stream *xdr, fsh_mode *ptr)
+{
+ u32 val;
+
+ if (xdr_stream_decode_u32(xdr, &val) < 0)
+ return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case fsm_DN:
+ case fsm_DR:
+ case fsm_DW:
+ case fsm_DRW:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fsh_access(struct xdr_stream *xdr, fsh_access *ptr)
+{
+ u32 val;
+
+ if (xdr_stream_decode_u32(xdr, &val) < 0)
+ return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case fsa_NONE:
+ case fsa_R:
+ case fsa_W:
+ case fsa_RW:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_share(struct xdr_stream *xdr, struct nlm_share *ptr)
+{
+ if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXSTRLEN))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->oh))
+ return false;
+ if (!xdrgen_decode_fsh_mode(xdr, &ptr->mode))
+ return false;
+ if (!xdrgen_decode_fsh_access(xdr, &ptr->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_shareargs(struct xdr_stream *xdr, struct nlm_shareargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm_share(xdr, &ptr->share))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->reclaim))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_shareres(struct xdr_stream *xdr, struct nlm_shareres *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm_stats(xdr, &ptr->stat))
+ return false;
+ if (!xdrgen_decode_int(xdr, &ptr->sequence))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_notify(struct xdr_stream *xdr, struct nlm_notify *ptr)
+{
+ if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXNAMELEN))
+ return false;
+ if (!xdrgen_decode_long(xdr, &ptr->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm_notifyargs(struct xdr_stream *xdr, struct nlm_notifyargs *ptr)
+{
+ if (!xdrgen_decode_nlm_notify(xdr, &ptr->notify))
+ return false;
+ if (xdr_stream_decode_opaque_fixed(xdr, ptr->private, SM_PRIV_SIZE) < 0)
+ return false;
+ return true;
+}
+
+/**
+ * nlm_svc_decode_nlm_testargs - Decode a nlm_testargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_testargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_testargs(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_lockargs - Decode a nlm_lockargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_lockargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_lockargs(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_cancargs - Decode a nlm_cancargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_cancargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_cancargs(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_unlockargs - Decode a nlm_unlockargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_unlockargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_unlockargs(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_testres - Decode a nlm_testres argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_testres *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_testres(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_res - Decode a nlm_res argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_res *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_res(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_notifyargs - Decode a nlm_notifyargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_notifyargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_notifyargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_notifyargs(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_shareargs - Decode a nlm_shareargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_shareargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_shareargs(xdr, argp);
+}
+
+/**
+ * nlm_svc_decode_nlm_notify - Decode a nlm_notify argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm_svc_decode_nlm_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_notify *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm_notify(xdr, argp);
+}
+
+static bool __maybe_unused
+xdrgen_encode_netobj(struct xdr_stream *xdr, const netobj value)
+{
+ return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_stats(struct xdr_stream *xdr, nlm_stats value)
+{
+ return xdr_stream_encode_be32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_stat(struct xdr_stream *xdr, const struct nlm_stat *value)
+{
+ if (!xdrgen_encode_nlm_stats(xdr, value->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_res(struct xdr_stream *xdr, const struct nlm_res *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm_stat(xdr, &value->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_holder(struct xdr_stream *xdr, const struct nlm_holder *value)
+{
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_int(xdr, value->uppid))
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->oh))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->l_offset))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_testrply(struct xdr_stream *xdr, const struct nlm_testrply *ptr)
+{
+ if (!xdrgen_encode_nlm_stats(xdr, ptr->stat))
+ return false;
+ switch (ptr->stat) {
+ case __constant_cpu_to_be32(LCK_DENIED):
+ if (!xdrgen_encode_nlm_holder(xdr, &ptr->u.holder))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_testres(struct xdr_stream *xdr, const struct nlm_testres *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm_testrply(xdr, &value->test_stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_lock(struct xdr_stream *xdr, const struct nlm_lock *value)
+{
+ if (value->caller_name.len > LM_MAXSTRLEN)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->caller_name.data, value->caller_name.len) < 0)
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->oh))
+ return false;
+ if (!xdrgen_encode_int(xdr, value->uppid))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->l_offset))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_lockargs(struct xdr_stream *xdr, const struct nlm_lockargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->block))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_nlm_lock(xdr, &value->alock))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->reclaim))
+ return false;
+ if (!xdrgen_encode_int(xdr, value->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_cancargs(struct xdr_stream *xdr, const struct nlm_cancargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->block))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_nlm_lock(xdr, &value->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_testargs(struct xdr_stream *xdr, const struct nlm_testargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_nlm_lock(xdr, &value->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_unlockargs(struct xdr_stream *xdr, const struct nlm_unlockargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm_lock(xdr, &value->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fsh_mode(struct xdr_stream *xdr, fsh_mode value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fsh_access(struct xdr_stream *xdr, fsh_access value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_share(struct xdr_stream *xdr, const struct nlm_share *value)
+{
+ if (value->caller_name.len > LM_MAXSTRLEN)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->caller_name.data, value->caller_name.len) < 0)
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->oh))
+ return false;
+ if (!xdrgen_encode_fsh_mode(xdr, value->mode))
+ return false;
+ if (!xdrgen_encode_fsh_access(xdr, value->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_shareargs(struct xdr_stream *xdr, const struct nlm_shareargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm_share(xdr, &value->share))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->reclaim))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_shareres(struct xdr_stream *xdr, const struct nlm_shareres *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm_stats(xdr, value->stat))
+ return false;
+ if (!xdrgen_encode_int(xdr, value->sequence))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_notify(struct xdr_stream *xdr, const struct nlm_notify *value)
+{
+ if (value->name.len > LM_MAXNAMELEN)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->name.data, value->name.len) < 0)
+ return false;
+ if (!xdrgen_encode_long(xdr, value->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm_notifyargs(struct xdr_stream *xdr, const struct nlm_notifyargs *value)
+{
+ if (!xdrgen_encode_nlm_notify(xdr, &value->notify))
+ return false;
+ if (xdr_stream_encode_opaque_fixed(xdr, value->private, SM_PRIV_SIZE) < 0)
+ return false;
+ return true;
+}
+
+/**
+ * nlm_svc_encode_nlm_testres - Encode a nlm_testres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nlm_svc_encode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_testres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nlm_testres(xdr, resp);
+}
+
+/**
+ * nlm_svc_encode_nlm_res - Encode a nlm_res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nlm_svc_encode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nlm_res(xdr, resp);
+}
+
+/**
+ * nlm_svc_encode_nlm_shareres - Encode a nlm_shareres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nlm_svc_encode_nlm_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm_shareres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nlm_shareres(xdr, resp);
+}
diff --git a/fs/lockd/nlm3xdr_gen.h b/fs/lockd/nlm3xdr_gen.h
new file mode 100644
index 000000000..bdbfc26ba
--- /dev/null
+++ b/fs/lockd/nlm3xdr_gen.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x */
+/* XDR specification modification time: Mon Jun 29 20:42:29 2026 */
+
+#ifndef _LINUX_XDRGEN_NLM3_DECL_H
+#define _LINUX_XDRGEN_NLM3_DECL_H
+
+#include <linux/types.h>
+
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/xdrgen/_defs.h>
+#include <linux/sunrpc/xdrgen/_builtins.h>
+#include <linux/sunrpc/xdrgen/nlm3.h>
+
+bool nlm_svc_decode_nlm_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_notifyargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_decode_nlm_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+bool nlm_svc_encode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_encode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm_svc_encode_nlm_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+#endif /* _LINUX_XDRGEN_NLM3_DECL_H */
diff --git a/fs/lockd/nlm4xdr_gen.c b/fs/lockd/nlm4xdr_gen.c
new file mode 100644
index 000000000..f98d74ee3
--- /dev/null
+++ b/fs/lockd/nlm4xdr_gen.c
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: GPL-2.0
+// Generated by xdrgen. Manual edits will be lost.
+// XDR specification file: ../../Documentation/sunrpc/xdr/nlm4.x
+// XDR specification modification time: Mon Jun 29 20:42:29 2026
+
+#include <linux/sunrpc/svc.h>
+
+#include "nlm4xdr_gen.h"
+
+static bool __maybe_unused
+xdrgen_decode_netobj(struct xdr_stream *xdr, netobj *ptr)
+{
+ return xdrgen_decode_opaque(xdr, ptr, MAXNETOBJ_SZ);
+}
+
+static bool __maybe_unused
+xdrgen_decode_fsh4_mode(struct xdr_stream *xdr, fsh4_mode *ptr)
+{
+ u32 val;
+
+ if (xdr_stream_decode_u32(xdr, &val) < 0)
+ return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case fsm_DN:
+ case fsm_DR:
+ case fsm_DW:
+ case fsm_DRW:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fsh4_access(struct xdr_stream *xdr, fsh4_access *ptr)
+{
+ u32 val;
+
+ if (xdr_stream_decode_u32(xdr, &val) < 0)
+ return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case fsa_NONE:
+ case fsa_R:
+ case fsa_W:
+ case fsa_RW:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_uint64(struct xdr_stream *xdr, uint64 *ptr)
+{
+ return xdrgen_decode_unsigned_hyper(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_int64(struct xdr_stream *xdr, int64 *ptr)
+{
+ return xdrgen_decode_hyper(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_uint32(struct xdr_stream *xdr, uint32 *ptr)
+{
+ return xdrgen_decode_unsigned_long(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_int32(struct xdr_stream *xdr, int32 *ptr)
+{
+ return xdrgen_decode_long(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_stats(struct xdr_stream *xdr, nlm4_stats *ptr)
+{
+ __be32 raw;
+ u32 val;
+
+ if (xdr_stream_decode_be32(xdr, &raw) < 0)
+ return false;
+ val = be32_to_cpu(raw);
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case NLM4_GRANTED:
+ case NLM4_DENIED:
+ case NLM4_DENIED_NOLOCKS:
+ case NLM4_BLOCKED:
+ case NLM4_DENIED_GRACE_PERIOD:
+ case NLM4_DEADLCK:
+ case NLM4_ROFS:
+ case NLM4_STALE_FH:
+ case NLM4_FBIG:
+ case NLM4_FAILED:
+ break;
+ default:
+ return false;
+ }
+ *ptr = raw;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_holder(struct xdr_stream *xdr, struct nlm4_holder *ptr)
+{
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_int32(xdr, &ptr->svid))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->oh))
+ return false;
+ if (!xdrgen_decode_uint64(xdr, &ptr->l_offset))
+ return false;
+ if (!xdrgen_decode_uint64(xdr, &ptr->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_testrply(struct xdr_stream *xdr, struct nlm4_testrply *ptr)
+{
+ if (!xdrgen_decode_nlm4_stats(xdr, &ptr->stat))
+ return false;
+ switch (ptr->stat) {
+ case __constant_cpu_to_be32(NLM4_DENIED):
+ if (!xdrgen_decode_nlm4_holder(xdr, &ptr->u.holder))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_stat(struct xdr_stream *xdr, struct nlm4_stat *ptr)
+{
+ if (!xdrgen_decode_nlm4_stats(xdr, &ptr->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_res(struct xdr_stream *xdr, struct nlm4_res *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm4_stat(xdr, &ptr->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_testres(struct xdr_stream *xdr, struct nlm4_testres *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm4_testrply(xdr, &ptr->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_lock(struct xdr_stream *xdr, struct nlm4_lock *ptr)
+{
+ if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXSTRLEN))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->oh))
+ return false;
+ if (!xdrgen_decode_int32(xdr, &ptr->svid))
+ return false;
+ if (!xdrgen_decode_uint64(xdr, &ptr->l_offset))
+ return false;
+ if (!xdrgen_decode_uint64(xdr, &ptr->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_lockargs(struct xdr_stream *xdr, struct nlm4_lockargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->block))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_nlm4_lock(xdr, &ptr->alock))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->reclaim))
+ return false;
+ if (!xdrgen_decode_int32(xdr, &ptr->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_cancargs(struct xdr_stream *xdr, struct nlm4_cancargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->block))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_nlm4_lock(xdr, &ptr->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_testargs(struct xdr_stream *xdr, struct nlm4_testargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->exclusive))
+ return false;
+ if (!xdrgen_decode_nlm4_lock(xdr, &ptr->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_unlockargs(struct xdr_stream *xdr, struct nlm4_unlockargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm4_lock(xdr, &ptr->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_share(struct xdr_stream *xdr, struct nlm4_share *ptr)
+{
+ if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXSTRLEN))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_netobj(xdr, &ptr->oh))
+ return false;
+ if (!xdrgen_decode_fsh4_mode(xdr, &ptr->mode))
+ return false;
+ if (!xdrgen_decode_fsh4_access(xdr, &ptr->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_shareargs(struct xdr_stream *xdr, struct nlm4_shareargs *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm4_share(xdr, &ptr->share))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->reclaim))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_shareres(struct xdr_stream *xdr, struct nlm4_shareres *ptr)
+{
+ if (!xdrgen_decode_netobj(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_nlm4_stats(xdr, &ptr->stat))
+ return false;
+ if (!xdrgen_decode_int32(xdr, &ptr->sequence))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_notify(struct xdr_stream *xdr, struct nlm4_notify *ptr)
+{
+ if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXNAMELEN))
+ return false;
+ if (!xdrgen_decode_int32(xdr, &ptr->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nlm4_notifyargs(struct xdr_stream *xdr, struct nlm4_notifyargs *ptr)
+{
+ if (!xdrgen_decode_nlm4_notify(xdr, &ptr->notify))
+ return false;
+ if (xdr_stream_decode_opaque_fixed(xdr, ptr->private, SM_PRIV_SIZE) < 0)
+ return false;
+ return true;
+}
+
+/**
+ * nlm4_svc_decode_nlm4_testargs - Decode a nlm4_testargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_testargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_testargs(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_lockargs - Decode a nlm4_lockargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_lockargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_lockargs(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_cancargs - Decode a nlm4_cancargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_cancargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_cancargs(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_unlockargs - Decode a nlm4_unlockargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_unlockargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_unlockargs(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_testres - Decode a nlm4_testres argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_testres *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_testres(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_res - Decode a nlm4_res argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_res *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_res(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_notifyargs - Decode a nlm4_notifyargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_notifyargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_notifyargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_notifyargs(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_shareargs - Decode a nlm4_shareargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_shareargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_shareargs(xdr, argp);
+}
+
+/**
+ * nlm4_svc_decode_nlm4_notify - Decode a nlm4_notify argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nlm4_svc_decode_nlm4_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_notify *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_nlm4_notify(xdr, argp);
+}
+
+static bool __maybe_unused
+xdrgen_encode_netobj(struct xdr_stream *xdr, const netobj value)
+{
+ return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fsh4_mode(struct xdr_stream *xdr, fsh4_mode value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fsh4_access(struct xdr_stream *xdr, fsh4_access value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_uint64(struct xdr_stream *xdr, const uint64 value)
+{
+ return xdrgen_encode_unsigned_hyper(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_int64(struct xdr_stream *xdr, const int64 value)
+{
+ return xdrgen_encode_hyper(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_uint32(struct xdr_stream *xdr, const uint32 value)
+{
+ return xdrgen_encode_unsigned_long(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_int32(struct xdr_stream *xdr, const int32 value)
+{
+ return xdrgen_encode_long(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_stats(struct xdr_stream *xdr, nlm4_stats value)
+{
+ return xdr_stream_encode_be32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_holder(struct xdr_stream *xdr, const struct nlm4_holder *value)
+{
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_int32(xdr, value->svid))
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->oh))
+ return false;
+ if (!xdrgen_encode_uint64(xdr, value->l_offset))
+ return false;
+ if (!xdrgen_encode_uint64(xdr, value->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_testrply(struct xdr_stream *xdr, const struct nlm4_testrply *ptr)
+{
+ if (!xdrgen_encode_nlm4_stats(xdr, ptr->stat))
+ return false;
+ switch (ptr->stat) {
+ case __constant_cpu_to_be32(NLM4_DENIED):
+ if (!xdrgen_encode_nlm4_holder(xdr, &ptr->u.holder))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_stat(struct xdr_stream *xdr, const struct nlm4_stat *value)
+{
+ if (!xdrgen_encode_nlm4_stats(xdr, value->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_res(struct xdr_stream *xdr, const struct nlm4_res *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm4_stat(xdr, &value->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_testres(struct xdr_stream *xdr, const struct nlm4_testres *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm4_testrply(xdr, &value->stat))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_lock(struct xdr_stream *xdr, const struct nlm4_lock *value)
+{
+ if (value->caller_name.len > LM_MAXSTRLEN)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->caller_name.data, value->caller_name.len) < 0)
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->oh))
+ return false;
+ if (!xdrgen_encode_int32(xdr, value->svid))
+ return false;
+ if (!xdrgen_encode_uint64(xdr, value->l_offset))
+ return false;
+ if (!xdrgen_encode_uint64(xdr, value->l_len))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_lockargs(struct xdr_stream *xdr, const struct nlm4_lockargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->block))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_nlm4_lock(xdr, &value->alock))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->reclaim))
+ return false;
+ if (!xdrgen_encode_int32(xdr, value->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_cancargs(struct xdr_stream *xdr, const struct nlm4_cancargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->block))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_nlm4_lock(xdr, &value->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_testargs(struct xdr_stream *xdr, const struct nlm4_testargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->exclusive))
+ return false;
+ if (!xdrgen_encode_nlm4_lock(xdr, &value->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_unlockargs(struct xdr_stream *xdr, const struct nlm4_unlockargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm4_lock(xdr, &value->alock))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_share(struct xdr_stream *xdr, const struct nlm4_share *value)
+{
+ if (value->caller_name.len > LM_MAXSTRLEN)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->caller_name.data, value->caller_name.len) < 0)
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_netobj(xdr, value->oh))
+ return false;
+ if (!xdrgen_encode_fsh4_mode(xdr, value->mode))
+ return false;
+ if (!xdrgen_encode_fsh4_access(xdr, value->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_shareargs(struct xdr_stream *xdr, const struct nlm4_shareargs *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm4_share(xdr, &value->share))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->reclaim))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_shareres(struct xdr_stream *xdr, const struct nlm4_shareres *value)
+{
+ if (!xdrgen_encode_netobj(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_nlm4_stats(xdr, value->stat))
+ return false;
+ if (!xdrgen_encode_int32(xdr, value->sequence))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_notify(struct xdr_stream *xdr, const struct nlm4_notify *value)
+{
+ if (value->name.len > LM_MAXNAMELEN)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->name.data, value->name.len) < 0)
+ return false;
+ if (!xdrgen_encode_int32(xdr, value->state))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nlm4_notifyargs(struct xdr_stream *xdr, const struct nlm4_notifyargs *value)
+{
+ if (!xdrgen_encode_nlm4_notify(xdr, &value->notify))
+ return false;
+ if (xdr_stream_encode_opaque_fixed(xdr, value->private, SM_PRIV_SIZE) < 0)
+ return false;
+ return true;
+}
+
+/**
+ * nlm4_svc_encode_nlm4_testres - Encode a nlm4_testres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nlm4_svc_encode_nlm4_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_testres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nlm4_testres(xdr, resp);
+}
+
+/**
+ * nlm4_svc_encode_nlm4_res - Encode a nlm4_res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nlm4_svc_encode_nlm4_res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nlm4_res(xdr, resp);
+}
+
+/**
+ * nlm4_svc_encode_nlm4_shareres - Encode a nlm4_shareres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nlm4_svc_encode_nlm4_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct nlm4_shareres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nlm4_shareres(xdr, resp);
+}
diff --git a/fs/lockd/nlm4xdr_gen.h b/fs/lockd/nlm4xdr_gen.h
new file mode 100644
index 000000000..1a72c26ad
--- /dev/null
+++ b/fs/lockd/nlm4xdr_gen.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nlm4.x */
+/* XDR specification modification time: Mon Jun 29 20:42:29 2026 */
+
+#ifndef _LINUX_XDRGEN_NLM4_DECL_H
+#define _LINUX_XDRGEN_NLM4_DECL_H
+
+#include <linux/types.h>
+
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/xdrgen/_defs.h>
+#include <linux/sunrpc/xdrgen/_builtins.h>
+#include <linux/sunrpc/xdrgen/nlm4.h>
+
+bool nlm4_svc_decode_nlm4_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_notifyargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_decode_nlm4_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+bool nlm4_svc_encode_nlm4_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_encode_nlm4_res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nlm4_svc_encode_nlm4_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+#endif /* _LINUX_XDRGEN_NLM4_DECL_H */
diff --git a/fs/lockd/procfs.c b/fs/lockd/procfs.c
new file mode 100644
index 000000000..a01f08c8c
--- /dev/null
+++ b/fs/lockd/procfs.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Procfs support for lockd
+ *
+ * Copyright (c) 2014 Jeff Layton <jlayton@primarydata.com>
+ */
+
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <linux/nsproxy.h>
+#include <net/net_namespace.h>
+
+#include "netns.h"
+#include "procfs.h"
+
+/*
+ * We only allow strings that start with 'Y', 'y', or '1'.
+ */
+static ssize_t
+nlm_end_grace_write(struct file *file, const char __user *buf, size_t size,
+ loff_t *pos)
+{
+ char *data;
+ struct lockd_net *ln = net_generic(current->nsproxy->net_ns,
+ lockd_net_id);
+
+ if (size < 1)
+ return -EINVAL;
+
+ data = simple_transaction_get(file, buf, size);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ switch(data[0]) {
+ case 'Y':
+ case 'y':
+ case '1':
+ locks_end_grace(&ln->lockd_manager);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return size;
+}
+
+static ssize_t
+nlm_end_grace_read(struct file *file, char __user *buf, size_t size,
+ loff_t *pos)
+{
+ struct lockd_net *ln = net_generic(current->nsproxy->net_ns,
+ lockd_net_id);
+ char resp[3];
+
+ resp[0] = list_empty(&ln->lockd_manager.list) ? 'Y' : 'N';
+ resp[1] = '\n';
+ resp[2] = '\0';
+
+ return simple_read_from_buffer(buf, size, pos, resp, sizeof(resp));
+}
+
+static const struct proc_ops lockd_end_grace_proc_ops = {
+ .proc_write = nlm_end_grace_write,
+ .proc_read = nlm_end_grace_read,
+ .proc_lseek = default_llseek,
+ .proc_release = simple_transaction_release,
+};
+
+int __init
+lockd_create_procfs(void)
+{
+ struct proc_dir_entry *entry;
+
+ entry = proc_mkdir("fs/lockd", NULL);
+ if (!entry)
+ return -ENOMEM;
+ entry = proc_create("nlm_end_grace", S_IRUGO|S_IWUSR, entry,
+ &lockd_end_grace_proc_ops);
+ if (!entry) {
+ remove_proc_entry("fs/lockd", NULL);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+void __exit
+lockd_remove_procfs(void)
+{
+ remove_proc_entry("fs/lockd/nlm_end_grace", NULL);
+ remove_proc_entry("fs/lockd", NULL);
+}
diff --git a/fs/lockd/procfs.h b/fs/lockd/procfs.h
new file mode 100644
index 000000000..ba9a82f4c
--- /dev/null
+++ b/fs/lockd/procfs.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Procfs support for lockd
+ *
+ * Copyright (c) 2014 Jeff Layton <jlayton@primarydata.com>
+ */
+#ifndef _LOCKD_PROCFS_H
+#define _LOCKD_PROCFS_H
+
+#if IS_ENABLED(CONFIG_PROC_FS)
+int lockd_create_procfs(void);
+void lockd_remove_procfs(void);
+#else
+static inline int
+lockd_create_procfs(void)
+{
+ return 0;
+}
+
+static inline void
+lockd_remove_procfs(void)
+{
+ return;
+}
+#endif /* IS_ENABLED(CONFIG_PROC_FS) */
+
+#endif /* _LOCKD_PROCFS_H */
diff --git a/fs/lockd/share.h b/fs/lockd/share.h
new file mode 100644
index 000000000..a12b6c454
--- /dev/null
+++ b/fs/lockd/share.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DOS share management for lockd.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#ifndef _LOCKD_SHARE_H
+#define _LOCKD_SHARE_H
+
+#include <linux/bits.h>
+
+/* Synthetic svid for lockowner lookup during share operations */
+#define LOCKD_SHARE_SVID (~(u32)0)
+
+/* One bit per (access, deny) pair; index = (access << 2) | deny */
+#define LOCKD_FSH_BIT(a, d) BIT(((a) << 2) | (d))
+
+/*
+ * DOS share for a specific file
+ */
+struct lockd_share {
+ struct lockd_share * s_next; /* linked list */
+ struct nlm_host * s_host; /* client host */
+ struct nlm_file * s_file; /* shared file */
+ struct xdr_netobj s_owner; /* owner handle */
+ u32 s_access; /* access mode */
+ u32 s_mode; /* deny mode */
+ u16 s_access_deny_bmap; /* held (access, deny) pairs */
+};
+
+__be32 nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file,
+ struct xdr_netobj *oh, u32 access, u32 mode);
+__be32 nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
+ struct xdr_netobj *oh, u32 access, u32 mode);
+void nlmsvc_traverse_shares(struct nlm_host *, struct nlm_file *,
+ nlm_host_match_fn_t);
+
+#endif /* _LOCKD_SHARE_H */
diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c
new file mode 100644
index 000000000..ee90e7430
--- /dev/null
+++ b/fs/lockd/svc.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/fs/lockd/svc.c
+ *
+ * This is the central lockd service.
+ *
+ * FIXME: Separate the lockd NFS server functionality from the lockd NFS
+ * client functionality. Oh why didn't Sun create two separate
+ * services in the first place?
+ *
+ * Authors: Olaf Kirch (okir@monad.swb.de)
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sysctl.h>
+#include <linux/moduleparam.h>
+
+#include <linux/sched/signal.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/uio.h>
+#include <linux/smp.h>
+#include <linux/mutex.h>
+#include <linux/freezer.h>
+#include <linux/inetdevice.h>
+
+#include <linux/sunrpc/types.h>
+#include <linux/sunrpc/stats.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/sunrpc/svcsock.h>
+#include <linux/sunrpc/svc_xprt.h>
+#include <net/ip.h>
+#include <net/addrconf.h>
+#include <net/ipv6.h>
+#include <linux/nfs.h>
+
+#include "lockd.h"
+#include "netns.h"
+#include "procfs.h"
+#include "netlink.h"
+
+#define NLMDBG_FACILITY NLMDBG_SVC
+
+static struct svc_program nlmsvc_program;
+
+const struct nlmsvc_binding __rcu *nlmsvc_ops;
+EXPORT_SYMBOL_GPL(nlmsvc_ops);
+
+static DEFINE_MUTEX(nlmsvc_mutex);
+static unsigned int nlmsvc_users;
+static struct svc_serv *nlmsvc_serv;
+
+static void nlmsvc_request_retry(struct timer_list *tl)
+{
+ svc_wake_up(nlmsvc_serv);
+}
+DEFINE_TIMER(nlmsvc_retry, nlmsvc_request_retry);
+
+unsigned int lockd_net_id;
+
+/*
+ * These can be set at insmod time (useful for NFS as root filesystem),
+ * and also changed through the sysctl interface. -- Jamie Lokier, Aug 2003
+ */
+static unsigned long nlm_grace_period;
+unsigned long nlm_timeout = LOCKD_DFLT_TIMEO;
+static int nlm_udpport, nlm_tcpport;
+
+/*
+ * Constants needed for the sysctl interface.
+ */
+static const unsigned long nlm_grace_period_min = 0;
+static const unsigned long nlm_grace_period_max = 240;
+static const unsigned long nlm_timeout_min = 3;
+static const unsigned long nlm_timeout_max = 20;
+
+#ifdef CONFIG_SYSCTL
+static const int nlm_port_min = 0, nlm_port_max = 65535;
+static struct ctl_table_header * nlm_sysctl_table;
+#endif
+
+static unsigned long get_lockd_grace_period(struct net *net)
+{
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ /* Return the net-ns specific grace period, if there is one */
+ if (ln->gracetime)
+ return ln->gracetime * HZ;
+
+ /* Note: nlm_timeout should always be nonzero */
+ if (nlm_grace_period)
+ return roundup(nlm_grace_period, nlm_timeout) * HZ;
+ else
+ return nlm_timeout * 5 * HZ;
+}
+
+static void grace_ender(struct work_struct *grace)
+{
+ struct delayed_work *dwork = to_delayed_work(grace);
+ struct lockd_net *ln = container_of(dwork, struct lockd_net,
+ grace_period_end);
+
+ locks_end_grace(&ln->lockd_manager);
+}
+
+static void set_grace_period(struct net *net)
+{
+ unsigned long grace_period = get_lockd_grace_period(net);
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ locks_start_grace(net, &ln->lockd_manager);
+ cancel_delayed_work_sync(&ln->grace_period_end);
+ schedule_delayed_work(&ln->grace_period_end, grace_period);
+}
+
+/*
+ * This is the lockd kernel thread
+ */
+static int
+lockd(void *vrqstp)
+{
+ struct svc_rqst *rqstp = vrqstp;
+ struct net *net = &init_net;
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ svc_thread_init_status(rqstp, 0);
+
+ /* try_to_freeze() is called from svc_recv() */
+ set_freezable();
+
+ dprintk("NFS locking service started (ver " LOCKD_VERSION ").\n");
+
+ /*
+ * The main request loop. We don't terminate until the last
+ * NFS mount or NFS daemon has gone away.
+ */
+ while (!svc_thread_should_stop(rqstp)) {
+ nlmsvc_retry_blocked(rqstp);
+ svc_recv(rqstp, 0);
+ }
+ if (rcu_access_pointer(nlmsvc_ops))
+ nlmsvc_invalidate_all();
+ nlm_shutdown_hosts();
+ cancel_delayed_work_sync(&ln->grace_period_end);
+ locks_end_grace(&ln->lockd_manager);
+
+ dprintk("lockd_down: service stopped\n");
+
+ svc_exit_thread(rqstp);
+ return 0;
+}
+
+static int create_lockd_listener(struct svc_serv *serv, const char *name,
+ struct net *net, const int family,
+ const unsigned short port,
+ const struct cred *cred)
+{
+ struct svc_xprt *xprt;
+
+ xprt = svc_find_xprt(serv, name, net, family, 0);
+ if (xprt == NULL)
+ return svc_xprt_create(serv, name, net, family, port,
+ SVC_SOCK_DEFAULTS, cred);
+ svc_xprt_put(xprt);
+ return 0;
+}
+
+static int create_lockd_family(struct svc_serv *serv, struct net *net,
+ const int family, const struct cred *cred)
+{
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+ int err;
+
+ err = create_lockd_listener(serv, "udp", net, family,
+ ln->udp_port ? ln->udp_port : nlm_udpport, cred);
+ if (err < 0)
+ return err;
+
+ return create_lockd_listener(serv, "tcp", net, family,
+ ln->tcp_port ? ln->tcp_port : nlm_tcpport, cred);
+}
+
+/*
+ * Ensure there are active UDP and TCP listeners for lockd.
+ *
+ * Even if we have only TCP NFS mounts and/or TCP NFSDs, some
+ * local services (such as rpc.statd) still require UDP, and
+ * some NFS servers do not yet support NLM over TCP.
+ *
+ * Returns zero if all listeners are available; otherwise a
+ * negative errno value is returned.
+ */
+static int make_socks(struct svc_serv *serv, struct net *net,
+ const struct cred *cred)
+{
+ static int warned;
+ int err;
+
+ err = create_lockd_family(serv, net, PF_INET, cred);
+ if (err < 0)
+ goto out_err;
+
+ err = create_lockd_family(serv, net, PF_INET6, cred);
+ if (err < 0 && err != -EAFNOSUPPORT)
+ goto out_err;
+
+ warned = 0;
+ return 0;
+
+out_err:
+ if (warned++ == 0)
+ printk(KERN_WARNING
+ "lockd_up: makesock failed, error=%d\n", err);
+ svc_xprt_destroy_all(serv, net, true);
+ return err;
+}
+
+static int lockd_up_net(struct svc_serv *serv, struct net *net,
+ const struct cred *cred)
+{
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+ int error;
+
+ if (ln->nlmsvc_users++)
+ return 0;
+
+ error = svc_bind(serv, net);
+ if (error)
+ goto err_bind;
+
+ error = make_socks(serv, net, cred);
+ if (error < 0)
+ goto err_bind;
+ set_grace_period(net);
+ dprintk("%s: per-net data created; net=%x\n", __func__, net->ns.inum);
+ return 0;
+
+err_bind:
+ ln->nlmsvc_users--;
+ return error;
+}
+
+static void lockd_down_net(struct svc_serv *serv, struct net *net)
+{
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ if (ln->nlmsvc_users) {
+ if (--ln->nlmsvc_users == 0) {
+ nlm_shutdown_hosts_net(net);
+ cancel_delayed_work_sync(&ln->grace_period_end);
+ locks_end_grace(&ln->lockd_manager);
+ svc_xprt_destroy_all(serv, net, true);
+ }
+ } else {
+ pr_err("%s: no users! net=%x\n",
+ __func__, net->ns.inum);
+ BUG();
+ }
+}
+
+static int lockd_inetaddr_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct sockaddr_in sin;
+
+ if (event != NETDEV_DOWN)
+ goto out;
+
+ if (nlmsvc_serv) {
+ dprintk("lockd_inetaddr_event: removed %pI4\n",
+ &ifa->ifa_local);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = ifa->ifa_local;
+ svc_age_temp_xprts_now(nlmsvc_serv, (struct sockaddr *)&sin);
+ }
+
+out:
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block lockd_inetaddr_notifier = {
+ .notifier_call = lockd_inetaddr_event,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static int lockd_inet6addr_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr;
+ struct sockaddr_in6 sin6;
+
+ if (event != NETDEV_DOWN)
+ goto out;
+
+ if (nlmsvc_serv) {
+ dprintk("lockd_inet6addr_event: removed %pI6\n", &ifa->addr);
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = ifa->addr;
+ if (ipv6_addr_type(&sin6.sin6_addr) & IPV6_ADDR_LINKLOCAL)
+ sin6.sin6_scope_id = ifa->idev->dev->ifindex;
+ svc_age_temp_xprts_now(nlmsvc_serv, (struct sockaddr *)&sin6);
+ }
+
+out:
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block lockd_inet6addr_notifier = {
+ .notifier_call = lockd_inet6addr_event,
+};
+#endif
+
+static int lockd_get(void)
+{
+ struct svc_serv *serv;
+ unsigned int bufsize;
+ int error;
+
+ if (nlmsvc_serv) {
+ nlmsvc_users++;
+ return 0;
+ }
+
+ /*
+ * Sanity check: if there's no pid,
+ * we should be the first user ...
+ */
+ if (nlmsvc_users)
+ printk(KERN_WARNING
+ "lockd_up: no pid, %d users??\n", nlmsvc_users);
+
+#ifdef CONFIG_LOCKD_V4
+ bufsize = 1024 + max3(nlmsvc_version1.vs_xdrsize,
+ nlmsvc_version3.vs_xdrsize,
+ nlmsvc_version4.vs_xdrsize);
+#else
+ bufsize = 1024 + max(nlmsvc_version1.vs_xdrsize,
+ nlmsvc_version3.vs_xdrsize);
+#endif
+ serv = svc_create(&nlmsvc_program, bufsize, lockd);
+ if (!serv) {
+ printk(KERN_WARNING "lockd_up: create service failed\n");
+ return -ENOMEM;
+ }
+
+ error = svc_set_num_threads(serv, 0, 1);
+ if (error < 0) {
+ svc_destroy(&serv);
+ return error;
+ }
+
+ nlmsvc_serv = serv;
+ register_inetaddr_notifier(&lockd_inetaddr_notifier);
+#if IS_ENABLED(CONFIG_IPV6)
+ register_inet6addr_notifier(&lockd_inet6addr_notifier);
+#endif
+ dprintk("lockd_up: service created\n");
+ nlmsvc_users++;
+ return 0;
+}
+
+static void lockd_put(void)
+{
+ if (WARN(nlmsvc_users <= 0, "lockd_down: no users!\n"))
+ return;
+ if (--nlmsvc_users)
+ return;
+
+ unregister_inetaddr_notifier(&lockd_inetaddr_notifier);
+#if IS_ENABLED(CONFIG_IPV6)
+ unregister_inet6addr_notifier(&lockd_inet6addr_notifier);
+#endif
+
+ svc_set_num_threads(nlmsvc_serv, 0, 0);
+ timer_delete_sync(&nlmsvc_retry);
+ svc_destroy(&nlmsvc_serv);
+ dprintk("lockd_down: service destroyed\n");
+}
+
+/*
+ * Bring up the lockd process if it's not already up.
+ */
+int lockd_up(struct net *net, const struct cred *cred)
+{
+ int error;
+
+ mutex_lock(&nlmsvc_mutex);
+
+ error = lockd_get();
+ if (error)
+ goto err;
+
+ error = lockd_up_net(nlmsvc_serv, net, cred);
+ if (error < 0) {
+ lockd_put();
+ goto err;
+ }
+
+err:
+ mutex_unlock(&nlmsvc_mutex);
+ return error;
+}
+EXPORT_SYMBOL_GPL(lockd_up);
+
+/*
+ * Decrement the user count and bring down lockd if we're the last.
+ */
+void
+lockd_down(struct net *net)
+{
+ mutex_lock(&nlmsvc_mutex);
+ lockd_down_net(nlmsvc_serv, net);
+ lockd_put();
+ mutex_unlock(&nlmsvc_mutex);
+}
+EXPORT_SYMBOL_GPL(lockd_down);
+
+#ifdef CONFIG_SYSCTL
+
+/*
+ * Sysctl parameters (same as module parameters, different interface).
+ */
+
+static const struct ctl_table nlm_sysctls[] = {
+ {
+ .procname = "nlm_grace_period",
+ .data = &nlm_grace_period,
+ .maxlen = sizeof(unsigned long),
+ .mode = 0644,
+ .proc_handler = proc_doulongvec_minmax,
+ .extra1 = (unsigned long *) &nlm_grace_period_min,
+ .extra2 = (unsigned long *) &nlm_grace_period_max,
+ },
+ {
+ .procname = "nlm_timeout",
+ .data = &nlm_timeout,
+ .maxlen = sizeof(unsigned long),
+ .mode = 0644,
+ .proc_handler = proc_doulongvec_minmax,
+ .extra1 = (unsigned long *) &nlm_timeout_min,
+ .extra2 = (unsigned long *) &nlm_timeout_max,
+ },
+ {
+ .procname = "nlm_udpport",
+ .data = &nlm_udpport,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = (int *) &nlm_port_min,
+ .extra2 = (int *) &nlm_port_max,
+ },
+ {
+ .procname = "nlm_tcpport",
+ .data = &nlm_tcpport,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = (int *) &nlm_port_min,
+ .extra2 = (int *) &nlm_port_max,
+ },
+ {
+ .procname = "nsm_use_hostnames",
+ .data = &nsm_use_hostnames,
+ .maxlen = sizeof(bool),
+ .mode = 0644,
+ .proc_handler = proc_dobool,
+ },
+ {
+ .procname = "nsm_local_state",
+ .data = &nsm_local_state,
+ .maxlen = sizeof(nsm_local_state),
+ .mode = 0644,
+ .proc_handler = proc_douintvec,
+ .extra1 = SYSCTL_ZERO,
+ },
+};
+
+#endif /* CONFIG_SYSCTL */
+
+/*
+ * Module (and sysfs) parameters.
+ */
+
+#define param_set_min_max(name, type, which_strtol, min, max) \
+static int param_set_##name(const char *val, const struct kernel_param *kp) \
+{ \
+ char *endp; \
+ __typeof__(type) num = which_strtol(val, &endp, 0); \
+ if (endp == val || *endp || num < (min) || num > (max)) \
+ return -EINVAL; \
+ *((type *) kp->arg) = num; \
+ return 0; \
+}
+
+static inline int is_callback(u32 proc)
+{
+ return proc == NLMPROC_GRANTED
+ || proc == NLMPROC_GRANTED_MSG
+ || proc == NLMPROC_TEST_RES
+ || proc == NLMPROC_LOCK_RES
+ || proc == NLMPROC_CANCEL_RES
+ || proc == NLMPROC_UNLOCK_RES
+ || proc == NLMPROC_NSM_NOTIFY;
+}
+
+
+static enum svc_auth_status lockd_authenticate(struct svc_rqst *rqstp)
+{
+ rqstp->rq_client = NULL;
+ switch (rqstp->rq_authop->flavour) {
+ case RPC_AUTH_NULL:
+ case RPC_AUTH_UNIX:
+ rqstp->rq_auth_stat = rpc_auth_ok;
+ if (rqstp->rq_proc == 0)
+ return SVC_OK;
+ if (is_callback(rqstp->rq_proc)) {
+ /* Leave it to individual procedures to
+ * call nlmsvc_lookup_host(rqstp)
+ */
+ return SVC_OK;
+ }
+ return svc_set_client(rqstp);
+ }
+ rqstp->rq_auth_stat = rpc_autherr_badcred;
+ return SVC_DENIED;
+}
+
+
+param_set_min_max(port, int, simple_strtol, 0, 65535)
+param_set_min_max(grace_period, unsigned long, simple_strtoul,
+ nlm_grace_period_min, nlm_grace_period_max)
+param_set_min_max(timeout, unsigned long, simple_strtoul,
+ nlm_timeout_min, nlm_timeout_max)
+
+MODULE_AUTHOR("Olaf Kirch <okir@monad.swb.de>");
+MODULE_DESCRIPTION("NFS file locking service version " LOCKD_VERSION ".");
+MODULE_LICENSE("GPL");
+
+module_param_call(nlm_grace_period, param_set_grace_period, param_get_ulong,
+ &nlm_grace_period, 0644);
+module_param_call(nlm_timeout, param_set_timeout, param_get_ulong,
+ &nlm_timeout, 0644);
+module_param_call(nlm_udpport, param_set_port, param_get_int,
+ &nlm_udpport, 0644);
+module_param_call(nlm_tcpport, param_set_port, param_get_int,
+ &nlm_tcpport, 0644);
+module_param(nsm_use_hostnames, bool, 0644);
+
+static int lockd_init_net(struct net *net)
+{
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ INIT_DELAYED_WORK(&ln->grace_period_end, grace_ender);
+ INIT_LIST_HEAD(&ln->lockd_manager.list);
+ ln->lockd_manager.block_opens = false;
+ INIT_LIST_HEAD(&ln->nsm_handles);
+ return 0;
+}
+
+static void lockd_exit_net(struct net *net)
+{
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+
+ WARN_ONCE(!list_empty(&ln->lockd_manager.list),
+ "net %x %s: lockd_manager.list is not empty\n",
+ net->ns.inum, __func__);
+ WARN_ONCE(!list_empty(&ln->nsm_handles),
+ "net %x %s: nsm_handles list is not empty\n",
+ net->ns.inum, __func__);
+ WARN_ONCE(delayed_work_pending(&ln->grace_period_end),
+ "net %x %s: grace_period_end was not cancelled\n",
+ net->ns.inum, __func__);
+}
+
+static struct pernet_operations lockd_net_ops = {
+ .init = lockd_init_net,
+ .exit = lockd_exit_net,
+ .id = &lockd_net_id,
+ .size = sizeof(struct lockd_net),
+};
+
+
+/*
+ * Initialising and terminating the module.
+ */
+
+static int __init init_nlm(void)
+{
+ int err;
+
+#ifdef CONFIG_SYSCTL
+ err = -ENOMEM;
+ nlm_sysctl_table = register_sysctl("fs/nfs", nlm_sysctls);
+ if (nlm_sysctl_table == NULL)
+ goto err_sysctl;
+#endif
+ err = register_pernet_subsys(&lockd_net_ops);
+ if (err)
+ goto err_pernet;
+
+ err = genl_register_family(&lockd_nl_family);
+ if (err)
+ goto err_netlink;
+
+ err = lockd_create_procfs();
+ if (err)
+ goto err_procfs;
+
+ return 0;
+
+err_procfs:
+ genl_unregister_family(&lockd_nl_family);
+err_netlink:
+ unregister_pernet_subsys(&lockd_net_ops);
+err_pernet:
+#ifdef CONFIG_SYSCTL
+ unregister_sysctl_table(nlm_sysctl_table);
+err_sysctl:
+#endif
+ return err;
+}
+
+static void __exit exit_nlm(void)
+{
+ /* FIXME: delete all NLM clients */
+ nlm_shutdown_hosts();
+ genl_unregister_family(&lockd_nl_family);
+ lockd_remove_procfs();
+ unregister_pernet_subsys(&lockd_net_ops);
+#ifdef CONFIG_SYSCTL
+ unregister_sysctl_table(nlm_sysctl_table);
+#endif
+}
+
+module_init(init_nlm);
+module_exit(exit_nlm);
+
+/**
+ * nlmsvc_dispatch - Process an NLM Request
+ * @rqstp: incoming request
+ *
+ * Return values:
+ * %0: Processing complete; do not send a Reply
+ * %1: Processing complete; send Reply in rqstp->rq_res
+ */
+int nlmsvc_dispatch(struct svc_rqst *rqstp)
+{
+ const struct svc_procedure *procp = rqstp->rq_procinfo;
+ __be32 *statp = rqstp->rq_accept_statp;
+
+ if (!procp->pc_decode(rqstp, &rqstp->rq_arg_stream))
+ goto out_decode_err;
+
+ *statp = procp->pc_func(rqstp);
+ if (*statp == rpc_drop_reply)
+ return 0;
+ if (*statp != rpc_success)
+ return 1;
+
+ if (!procp->pc_encode(rqstp, &rqstp->rq_res_stream))
+ goto out_encode_err;
+
+ return 1;
+
+out_decode_err:
+ *statp = rpc_garbage_args;
+ return 1;
+
+out_encode_err:
+ *statp = rpc_system_err;
+ return 1;
+}
+
+/*
+ * Define NLM program and procedures
+ */
+static const struct svc_version *nlmsvc_version[] = {
+ [1] = &nlmsvc_version1,
+ [3] = &nlmsvc_version3,
+#ifdef CONFIG_LOCKD_V4
+ [4] = &nlmsvc_version4,
+#endif
+};
+
+#define NLM_NRVERS ARRAY_SIZE(nlmsvc_version)
+static struct svc_program nlmsvc_program = {
+ .pg_prog = NLM_PROGRAM, /* program number */
+ .pg_nvers = NLM_NRVERS, /* number of entries in nlmsvc_version */
+ .pg_vers = nlmsvc_version, /* version table */
+ .pg_name = "lockd", /* service name */
+ .pg_class = "nfsd", /* share authentication with nfsd */
+ .pg_authenticate = &lockd_authenticate, /* export authentication */
+ .pg_init_request = svc_generic_init_request,
+ .pg_rpcbind_set = svc_generic_rpcbind_set,
+};
+
+/**
+ * lockd_nl_server_set_doit - set the lockd server parameters via netlink
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * This updates the per-net values. When updating the values in the init_net
+ * namespace, also update the "legacy" global values.
+ *
+ * Return 0 on success or a negative errno.
+ */
+int lockd_nl_server_set_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+ const struct nlattr *attr;
+
+ if (GENL_REQ_ATTR_CHECK(info, LOCKD_A_SERVER_GRACETIME))
+ return -EINVAL;
+
+ if (info->attrs[LOCKD_A_SERVER_GRACETIME] ||
+ info->attrs[LOCKD_A_SERVER_TCP_PORT] ||
+ info->attrs[LOCKD_A_SERVER_UDP_PORT]) {
+ attr = info->attrs[LOCKD_A_SERVER_GRACETIME];
+ if (attr) {
+ u32 gracetime = nla_get_u32(attr);
+
+ if (gracetime > nlm_grace_period_max)
+ return -EINVAL;
+
+ ln->gracetime = gracetime;
+
+ if (net == &init_net)
+ nlm_grace_period = gracetime;
+ }
+
+ attr = info->attrs[LOCKD_A_SERVER_TCP_PORT];
+ if (attr) {
+ ln->tcp_port = nla_get_u16(attr);
+ if (net == &init_net)
+ nlm_tcpport = ln->tcp_port;
+ }
+
+ attr = info->attrs[LOCKD_A_SERVER_UDP_PORT];
+ if (attr) {
+ ln->udp_port = nla_get_u16(attr);
+ if (net == &init_net)
+ nlm_udpport = ln->udp_port;
+ }
+ }
+ return 0;
+}
+
+/**
+ * lockd_nl_server_get_doit - get lockd server parameters via netlink
+ * @skb: reply buffer
+ * @info: netlink metadata and command arguments
+ *
+ * Return 0 on success or a negative errno.
+ */
+int lockd_nl_server_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct lockd_net *ln = net_generic(net, lockd_net_id);
+ void *hdr;
+ int err;
+
+ skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ hdr = genlmsg_iput(skb, info);
+ if (!hdr) {
+ err = -EMSGSIZE;
+ goto err_free_msg;
+ }
+
+ err = nla_put_u32(skb, LOCKD_A_SERVER_GRACETIME, ln->gracetime) ||
+ nla_put_u16(skb, LOCKD_A_SERVER_TCP_PORT, ln->tcp_port) ||
+ nla_put_u16(skb, LOCKD_A_SERVER_UDP_PORT, ln->udp_port);
+ if (err)
+ goto err_free_msg;
+
+ genlmsg_end(skb, hdr);
+
+ return genlmsg_reply(skb, info);
+err_free_msg:
+ nlmsg_free(skb);
+
+ return err;
+}
diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c
new file mode 100644
index 000000000..03c5554ca
--- /dev/null
+++ b/fs/lockd/svc4proc.c
@@ -0,0 +1,1432 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/svc4proc.c
+ *
+ * Lockd server procedures. We don't implement the NLM_*_RES
+ * procedures because we don't use the async procedures.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/time.h>
+#include <linux/sunrpc/svc_xprt.h>
+
+#include "lockd.h"
+
+/*
+ * xdr.h defines SM_MAXSTRLEN and SM_PRIV_SIZE as macros.
+ * nlm4xdr_gen.h defines them as enum constants. Undefine the
+ * macros to allow the xdrgen enum definitions to be used.
+ */
+#undef SM_MAXSTRLEN
+#undef SM_PRIV_SIZE
+
+#include "share.h"
+#include "nlm4xdr_gen.h"
+
+/*
+ * Wrapper structures combine xdrgen types with legacy lockd_lock.
+ * The xdrgen field must be first so the structure can be cast
+ * to its XDR type for the RPC dispatch layer.
+ */
+struct nlm4_testargs_wrapper {
+ struct nlm4_testargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm4_testargs_wrapper, xdrgen) == 0);
+
+struct nlm4_lockargs_wrapper {
+ struct nlm4_lockargs xdrgen;
+ struct lockd_cookie cookie;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm4_lockargs_wrapper, xdrgen) == 0);
+
+struct nlm4_cancargs_wrapper {
+ struct nlm4_cancargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm4_cancargs_wrapper, xdrgen) == 0);
+
+struct nlm4_unlockargs_wrapper {
+ struct nlm4_unlockargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm4_unlockargs_wrapper, xdrgen) == 0);
+
+struct nlm4_notifyargs_wrapper {
+ struct nlm4_notifyargs xdrgen;
+ struct lockd_reboot reboot;
+};
+
+static_assert(offsetof(struct nlm4_notifyargs_wrapper, xdrgen) == 0);
+
+struct nlm4_notify_wrapper {
+ struct nlm4_notify xdrgen;
+};
+
+static_assert(offsetof(struct nlm4_notify_wrapper, xdrgen) == 0);
+
+struct nlm4_testres_wrapper {
+ struct nlm4_testres xdrgen;
+ struct lockd_lock lock;
+};
+
+struct nlm4_shareargs_wrapper {
+ struct nlm4_shareargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm4_shareargs_wrapper, xdrgen) == 0);
+
+static_assert(offsetof(struct nlm4_testres_wrapper, xdrgen) == 0);
+
+struct nlm4_res_wrapper {
+ struct nlm4_res xdrgen;
+ struct lockd_cookie cookie;
+};
+
+static_assert(offsetof(struct nlm4_res_wrapper, xdrgen) == 0);
+
+struct nlm4_shareres_wrapper {
+ struct nlm4_shareres xdrgen;
+};
+
+static_assert(offsetof(struct nlm4_shareres_wrapper, xdrgen) == 0);
+
+static __be32
+nlm4_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object)
+{
+ if (object->len > NLM_MAXCOOKIELEN)
+ return nlm_lck_denied_nolocks;
+ cookie->len = object->len;
+ memcpy(cookie->data, object->data, object->len);
+ return nlm_granted;
+}
+
+static __be32
+nlm4_lock_to_lockd_lock(struct lockd_lock *lock, struct nlm4_lock *alock)
+{
+ if (alock->fh.len > NFS_MAXFHSIZE)
+ return nlm_lck_denied;
+ lock->fh.size = alock->fh.len;
+ memcpy(lock->fh.data, alock->fh.data, alock->fh.len);
+ lock->oh.len = alock->oh.len;
+ lock->oh.data = alock->oh.data;
+ lock->svid = alock->svid;
+ lockd_set_file_lock_range4(&lock->fl, alock->l_offset, alock->l_len);
+ return nlm_granted;
+}
+
+static struct nlm_host *
+nlm4svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
+{
+ struct nlm_host *host;
+
+ if (!rcu_access_pointer(nlmsvc_ops))
+ return NULL;
+ host = nlmsvc_lookup_host(rqstp, caller.data, caller.len);
+ if (!host)
+ return NULL;
+ if (monitored && nsm_monitor(host) < 0) {
+ nlmsvc_release_host(host);
+ return NULL;
+ }
+ return host;
+}
+
+static __be32
+nlm4svc_lookup_file(struct svc_rqst *rqstp, struct nlm_host *host,
+ struct lockd_lock *lock, struct nlm_file **filp,
+ struct nlm4_lock *xdr_lock, unsigned char type)
+{
+ bool is_test = (rqstp->rq_proc == NLMPROC4_TEST ||
+ rqstp->rq_proc == NLMPROC4_TEST_MSG);
+ struct file_lock *fl = &lock->fl;
+ struct nlm_file *file = NULL;
+ int mode;
+ __be32 error;
+
+ if (xdr_lock->fh.len > NFS_MAXFHSIZE)
+ return nlm_lck_denied_nolocks;
+ lock->fh.size = xdr_lock->fh.len;
+ memcpy(lock->fh.data, xdr_lock->fh.data, xdr_lock->fh.len);
+ if (xdr_lock->fh.len < LOCKD_FH_HASH_SIZE)
+ memset(lock->fh.data + xdr_lock->fh.len, 0,
+ LOCKD_FH_HASH_SIZE - xdr_lock->fh.len);
+
+ lock->oh.len = xdr_lock->oh.len;
+ lock->oh.data = xdr_lock->oh.data;
+
+ lock->svid = xdr_lock->svid;
+ lock->lock_start = xdr_lock->l_offset;
+ lock->lock_len = xdr_lock->l_len;
+
+ if (lock->lock_start > OFFSET_MAX ||
+ (lock->lock_len && ((lock->lock_len - 1) > (OFFSET_MAX - lock->lock_start))))
+ return nlm4_fbig;
+
+ locks_init_lock(fl);
+ fl->c.flc_type = type;
+ lockd_set_file_lock_range4(fl, lock->lock_start, lock->lock_len);
+
+ mode = is_test ? O_RDWR : lock_to_openmode(fl);
+ error = nlm_lookup_file(rqstp, &file, lock, mode);
+ switch (error) {
+ case nlm_granted:
+ break;
+ case nlm__int__stale_fh:
+ return nlm4_stale_fh;
+ case nlm__int__failed:
+ return nlm4_failed;
+ default:
+ return error;
+ }
+ *filp = file;
+
+ fl->c.flc_flags = FL_POSIX;
+ fl->c.flc_file = is_test ? nlmsvc_file_file(file)
+ : file->f_file[mode];
+ fl->c.flc_pid = current->tgid;
+ fl->fl_lmops = &nlmsvc_lock_operations;
+ nlmsvc_locks_init_private(fl, host, (pid_t)lock->svid);
+ if (!fl->c.flc_owner)
+ return nlm_lck_denied_nolocks;
+
+ return nlm_granted;
+}
+
+/**
+ * nlm4svc_proc_null - NULL: Test for presence of service
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void NLMPROC4_NULL(void) = 0;
+ */
+static __be32
+nlm4svc_proc_null(struct svc_rqst *rqstp)
+{
+ return rpc_success;
+}
+
+/**
+ * nlm4svc_proc_test - TEST: Check for conflicting lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm4_testres NLMPROC4_TEST(nlm4_testargs) = 1;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The server would be able to grant the
+ * requested lock.
+ * %NLM4_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %NLM4_DENIED_NOLOCKS: The server could not allocate the resources
+ * needed to process the request.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32 nlm4svc_proc_test(struct svc_rqst *rqstp)
+{
+ struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm4_testres_wrapper *resp = rqstp->rq_resp;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ type);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmsvc_testlock(rqstp, file, host,
+ &argp->lock, &resp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+ if (resp->xdrgen.stat.stat == nlm_lck_denied) {
+ struct lockd_lock *conf = &resp->lock;
+ struct nlm4_holder *holder = &resp->xdrgen.stat.u.holder;
+
+ holder->exclusive = (conf->fl.c.flc_type != F_RDLCK);
+ holder->svid = conf->svid;
+ holder->oh.len = conf->oh.len;
+ holder->oh.data = conf->oh.data;
+ holder->l_offset = conf->fl.fl_start;
+ if (conf->fl.fl_end == OFFSET_MAX)
+ holder->l_len = 0;
+ else
+ holder->l_len = conf->fl.fl_end - conf->fl.fl_start + 1;
+ }
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+static __be32
+nlm4svc_do_lock(struct svc_rqst *rqstp, bool monitored)
+{
+ struct nlm4_lockargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm4_res_wrapper *resp = rqstp->rq_resp;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm4_netobj_to_cookie(&argp->cookie,
+ &argp->xdrgen.cookie);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name,
+ monitored);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ type);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmsvc_lock(rqstp, file, host, &argp->lock,
+ argp->xdrgen.block, &argp->cookie,
+ argp->xdrgen.reclaim);
+ if (resp->xdrgen.stat.stat == nlm__int__deadlock)
+ resp->xdrgen.stat.stat = nlm4_deadlock;
+
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_lock - LOCK: Establish a monitored lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm4_res NLMPROC4_LOCK(nlm4_lockargs) = 2;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The requested lock was granted.
+ * %NLM4_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %NLM4_DENIED_NOLOCKS: The server could not allocate the resources
+ * needed to process the request.
+ * %NLM4_BLOCKED: The blocking request cannot be granted
+ * immediately. The server will send an
+ * NLMPROC4_GRANTED callback to the client when
+ * the lock can be granted.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_DEADLCK: The request could not be granted and
+ * blocking would cause a deadlock.
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32
+nlm4svc_proc_lock(struct svc_rqst *rqstp)
+{
+ return nlm4svc_do_lock(rqstp, true);
+}
+
+/**
+ * nlm4svc_proc_cancel - CANCEL: Cancel an outstanding blocked lock request
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully
+ * %rpc_drop_reply: Do not send an RPC reply
+ *
+ * RPC synopsis:
+ * nlm4_res NLMPROC4_CANCEL(nlm4_cancargs) = 3;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_LCK_GRANTED: The requested lock was canceled.
+ * %NLM4_LCK_DENIED: There was no lock to cancel.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_DENIED_NOLOCKS: A needed resource could not be allocated.
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32
+nlm4svc_proc_cancel(struct svc_rqst *rqstp)
+{
+ struct nlm4_cancargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm4_res_wrapper *resp = rqstp->rq_resp;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ type);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmsvc_cancel_blocked(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_unlock - UNLOCK: Remove a lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm4_res NLMPROC4_UNLOCK(nlm4_unlockargs) = 4;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The requested lock was released.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_DENIED_NOLOCKS: A needed resource could not be allocated.
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32
+nlm4svc_proc_unlock(struct svc_rqst *rqstp)
+{
+ struct nlm4_unlockargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm4_res_wrapper *resp = rqstp->rq_resp;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ F_UNLCK);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmsvc_unlock(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_granted - GRANTED: Server grants a previously blocked lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ *
+ * RPC synopsis:
+ * nlm4_res NLMPROC4_GRANTED(nlm4_testargs) = 5;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The granted lock was accepted.
+ * %NLM4_DENIED: The procedure failed, possibly due to
+ * internal resource constraints.
+ * %NLM4_DENIED_GRACE_PERIOD: The client host recently restarted and
+ * its NLM is re-establishing existing locks,
+ * so it is not yet ready to accept callbacks.
+ */
+static __be32
+nlm4svc_proc_granted(struct svc_rqst *rqstp)
+{
+ struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm4_res_wrapper *resp = rqstp->rq_resp;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm4_lock_to_lockd_lock(&argp->lock,
+ &argp->xdrgen.alock);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmclnt_grant(svc_addr(rqstp), &argp->lock);
+
+out:
+ return rpc_success;
+}
+
+/*
+ * This is the generic lockd callback for async RPC calls
+ */
+static void nlm4svc_callback_exit(struct rpc_task *task, void *data)
+{
+}
+
+static void nlm4svc_callback_release(void *data)
+{
+ nlmsvc_release_call(data);
+}
+
+static const struct rpc_call_ops nlm4svc_callback_ops = {
+ .rpc_call_done = nlm4svc_callback_exit,
+ .rpc_release = nlm4svc_callback_release,
+};
+
+/*
+ * Dispatch an async callback RPC to a client with a pre-resolved host.
+ * Caller provides a reference to @host; this function takes ownership
+ * and releases it via nlmsvc_release_host() before returning.
+ */
+static __be32
+nlm4svc_callback(struct svc_rqst *rqstp, struct nlm_host *host, u32 proc,
+ __be32 (*func)(struct svc_rqst *, struct lockd_res *))
+{
+ struct nlm_rqst *call;
+ __be32 stat;
+
+ call = nlm_alloc_call(host);
+ nlmsvc_release_host(host);
+ if (call == NULL)
+ return rpc_system_err;
+
+ stat = func(rqstp, &call->a_res);
+ if (stat != 0) {
+ nlmsvc_release_call(call);
+ return stat;
+ }
+
+ call->a_flags = RPC_TASK_ASYNC;
+ if (nlm_async_reply(call, proc, &nlm4svc_callback_ops) < 0)
+ return rpc_system_err;
+ return rpc_success;
+}
+
+static __be32
+__nlm4svc_proc_test_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_lockowner *owner;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm4_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->status = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, type);
+ if (resp->status)
+ goto out;
+
+ owner = argp->lock.fl.c.flc_owner;
+ resp->status = nlmsvc_testlock(rqstp, file, host, &argp->lock,
+ &resp->lock);
+ nlmsvc_put_lockowner(owner);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_test_msg - TEST_MSG: Check for conflicting lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_TEST_MSG(nlm4_testargs) = 6;
+ *
+ * The response to this request is delivered via the TEST_RES procedure.
+ */
+static __be32 nlm4svc_proc_test_msg(struct svc_rqst *rqstp)
+{
+ struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlm4svc_callback(rqstp, host, NLMPROC4_TEST_RES,
+ __nlm4svc_proc_test_msg);
+}
+
+static __be32
+__nlm4svc_proc_lock_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm4_lockargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm4_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, true);
+ if (!host)
+ goto out;
+
+ resp->status = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, type);
+ if (resp->status)
+ goto out;
+
+ resp->status = nlmsvc_lock(rqstp, file, host, &argp->lock,
+ argp->xdrgen.block, &resp->cookie,
+ argp->xdrgen.reclaim);
+ if (resp->status == nlm__int__deadlock)
+ resp->status = nlm4_deadlock;
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_lock_msg - LOCK_MSG: Establish a monitored lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_LOCK_MSG(nlm4_lockargs) = 7;
+ *
+ * The response to this request is delivered via the LOCK_RES procedure.
+ */
+static __be32 nlm4svc_proc_lock_msg(struct svc_rqst *rqstp)
+{
+ struct nlm4_lockargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlm4svc_callback(rqstp, host, NLMPROC4_LOCK_RES,
+ __nlm4svc_proc_lock_msg);
+}
+
+static __be32
+__nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm4_cancargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm4_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ resp->status = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->status = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->status = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, type);
+ if (resp->status)
+ goto out;
+
+ resp->status = nlmsvc_cancel_blocked(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_cancel_msg - CANCEL_MSG: Cancel an outstanding lock request
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_CANCEL_MSG(nlm4_cancargs) = 8;
+ *
+ * The response to this request is delivered via the CANCEL_RES procedure.
+ */
+static __be32 nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp)
+{
+ struct nlm4_cancargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlm4svc_callback(rqstp, host, NLMPROC4_CANCEL_RES,
+ __nlm4svc_proc_cancel_msg);
+}
+
+static __be32
+__nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm4_unlockargs_wrapper *argp = rqstp->rq_argp;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm4_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ resp->status = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->status = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->status = nlm4svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, F_UNLCK);
+ if (resp->status)
+ goto out;
+
+ resp->status = nlmsvc_unlock(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_unlock_msg - UNLOCK_MSG: Remove an existing lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_UNLOCK_MSG(nlm4_unlockargs) = 9;
+ *
+ * The response to this request is delivered via the UNLOCK_RES procedure.
+ */
+static __be32 nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp)
+{
+ struct nlm4_unlockargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlm4svc_callback(rqstp, host, NLMPROC4_UNLOCK_RES,
+ __nlm4svc_proc_unlock_msg);
+}
+
+static __be32
+__nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
+
+ resp->status = nlm_lck_denied;
+ if (nlm4_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ if (nlm4_lock_to_lockd_lock(&argp->lock, &argp->xdrgen.alock))
+ goto out;
+
+ resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock);
+
+out:
+ return rpc_success;
+}
+
+/**
+ * nlm4svc_proc_granted_msg - GRANTED_MSG: Blocked lock has been granted
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_GRANTED_MSG(nlm4_testargs) = 10;
+ *
+ * The response to this request is delivered via the GRANTED_RES procedure.
+ */
+static __be32 nlm4svc_proc_granted_msg(struct svc_rqst *rqstp)
+{
+ struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlmsvc_lookup_host(rqstp, argp->xdrgen.alock.caller_name.data,
+ argp->xdrgen.alock.caller_name.len);
+ if (!host)
+ return rpc_system_err;
+
+ return nlm4svc_callback(rqstp, host, NLMPROC4_GRANTED_RES,
+ __nlm4svc_proc_granted_msg);
+}
+
+/**
+ * nlm4svc_proc_granted_res - GRANTED_RES: Lock Granted result
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_GRANTED_RES(nlm4_res) = 15;
+ */
+static __be32 nlm4svc_proc_granted_res(struct svc_rqst *rqstp)
+{
+ struct nlm4_res_wrapper *argp = rqstp->rq_argp;
+
+ if (!rcu_access_pointer(nlmsvc_ops))
+ return rpc_success;
+
+ if (nlm4_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie))
+ return rpc_success;
+ nlmsvc_grant_reply(&argp->cookie, argp->xdrgen.stat.stat);
+
+ return rpc_success;
+}
+
+/**
+ * nlm4svc_proc_sm_notify - SM_NOTIFY: Peer has rebooted
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * The SM_NOTIFY procedure is a private callback from Linux statd and is
+ * not part of the official NLM protocol.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_SM_NOTIFY(nlm4_notifyargs) = 16;
+ */
+static __be32 nlm4svc_proc_sm_notify(struct svc_rqst *rqstp)
+{
+ struct nlm4_notifyargs_wrapper *argp = rqstp->rq_argp;
+ struct lockd_reboot *reboot = &argp->reboot;
+
+ if (!nlm_privileged_requester(rqstp)) {
+ char buf[RPC_MAX_ADDRBUFLEN];
+
+ pr_warn("lockd: rejected NSM callback from %s\n",
+ svc_print_addr(rqstp, buf, sizeof(buf)));
+ return rpc_system_err;
+ }
+
+ reboot->len = argp->xdrgen.notify.name.len;
+ reboot->mon = (char *)argp->xdrgen.notify.name.data;
+ reboot->state = argp->xdrgen.notify.state;
+ memcpy(&reboot->priv.data, argp->xdrgen.private,
+ sizeof(reboot->priv.data));
+
+ nlm_host_rebooted(SVC_NET(rqstp), reboot);
+
+ return rpc_success;
+}
+
+/**
+ * nlm4svc_proc_unused - stub for unused procedures
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_proc_unavail: Program can't support procedure.
+ */
+static __be32 nlm4svc_proc_unused(struct svc_rqst *rqstp)
+{
+ return rpc_proc_unavail;
+}
+
+/**
+ * nlm4svc_proc_share - SHARE: Open a file using DOS file-sharing modes
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm4_shareres NLMPROC4_SHARE(nlm4_shareargs) = 20;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The requested share lock was granted.
+ * %NLM4_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_DENIED_NOLOCKS: A needed resource could not be allocated.
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32 nlm4svc_proc_share(struct svc_rqst *rqstp)
+{
+ struct nlm4_shareargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm4_shareres_wrapper *resp = rqstp->rq_resp;
+ struct lockd_lock *lock = &argp->lock;
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+ struct nlm4_lock xdr_lock = {
+ .fh = argp->xdrgen.share.fh,
+ .oh = argp->xdrgen.share.oh,
+ .svid = LOCKD_SHARE_SVID,
+ };
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(SVC_NET(rqstp)) && !argp->xdrgen.reclaim)
+ goto out;
+
+ resp->xdrgen.stat = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.share.caller_name, true);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat = nlm4svc_lookup_file(rqstp, host, lock, &file,
+ &xdr_lock, F_RDLCK);
+ if (resp->xdrgen.stat)
+ goto out;
+
+ resp->xdrgen.stat = nlmsvc_share_file(host, file, &lock->oh,
+ argp->xdrgen.share.access,
+ argp->xdrgen.share.mode);
+
+ nlmsvc_release_lockowner(lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_unshare - UNSHARE: Release a share reservation
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm4_shareres NLMPROC4_UNSHARE(nlm4_shareargs) = 21;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The share reservation was released.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_DENIED_NOLOCKS: A needed resource could not be allocated.
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32 nlm4svc_proc_unshare(struct svc_rqst *rqstp)
+{
+ struct nlm4_shareargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm4_shareres_wrapper *resp = rqstp->rq_resp;
+ struct lockd_lock *lock = &argp->lock;
+ struct nlm4_lock xdr_lock = {
+ .fh = argp->xdrgen.share.fh,
+ .oh = argp->xdrgen.share.oh,
+ .svid = LOCKD_SHARE_SVID,
+ };
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(SVC_NET(rqstp)))
+ goto out;
+
+ resp->xdrgen.stat = nlm_lck_denied_nolocks;
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.share.caller_name, true);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat = nlm4svc_lookup_file(rqstp, host, lock, &file,
+ &xdr_lock, F_RDLCK);
+ if (resp->xdrgen.stat)
+ goto out;
+
+ resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh,
+ argp->xdrgen.share.access,
+ argp->xdrgen.share.mode);
+
+ nlmsvc_release_lockowner(lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlm4svc_proc_nm_lock - NM_LOCK: Establish a non-monitored lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm4_res NLMPROC4_NM_LOCK(nlm4_lockargs) = 22;
+ *
+ * Permissible procedure status codes:
+ * %NLM4_GRANTED: The requested lock was granted.
+ * %NLM4_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %NLM4_DENIED_NOLOCKS: The server could not allocate the resources
+ * needed to process the request.
+ * %NLM4_BLOCKED: The blocking request cannot be granted
+ * immediately. The server will send an
+ * NLMPROC4_GRANTED callback to the client when
+ * the lock can be granted.
+ * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %NLM4_DEADLCK: The request could not be granted and
+ * blocking would cause a deadlock.
+ * %NLM4_STALE_FH: The request specified an invalid file handle.
+ * %NLM4_FBIG: The request specified a length or offset
+ * that exceeds the range supported by the
+ * server.
+ * %NLM4_FAILED: The request failed for an unspecified reason.
+ */
+static __be32 nlm4svc_proc_nm_lock(struct svc_rqst *rqstp)
+{
+ return nlm4svc_do_lock(rqstp, false);
+}
+
+/**
+ * nlm4svc_proc_free_all - FREE_ALL: Discard client's lock and share state
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ *
+ * RPC synopsis:
+ * void NLMPROC4_FREE_ALL(nlm4_notify) = 23;
+ */
+static __be32 nlm4svc_proc_free_all(struct svc_rqst *rqstp)
+{
+ struct nlm4_notify_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlm4svc_lookup_host(rqstp, argp->xdrgen.name, false);
+ if (!host)
+ goto out;
+
+ nlmsvc_free_host_resources(host);
+
+ nlmsvc_release_host(host);
+
+out:
+ return rpc_success;
+}
+
+
+/*
+ * NLMv4 Server procedures.
+ */
+
+static const struct svc_procedure nlm4svc_procedures[24] = {
+ [NLMPROC4_NULL] = {
+ .pc_func = nlm4svc_proc_null,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = XDR_void,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "NULL",
+ },
+ [NLMPROC4_TEST] = {
+ .pc_func = nlm4svc_proc_test,
+ .pc_decode = nlm4_svc_decode_nlm4_testargs,
+ .pc_encode = nlm4_svc_encode_nlm4_testres,
+ .pc_argsize = sizeof(struct nlm4_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_testres_wrapper),
+ .pc_xdrressize = NLM4_nlm4_testres_sz,
+ .pc_name = "TEST",
+ },
+ [NLMPROC4_LOCK] = {
+ .pc_func = nlm4svc_proc_lock,
+ .pc_decode = nlm4_svc_decode_nlm4_lockargs,
+ .pc_encode = nlm4_svc_encode_nlm4_res,
+ .pc_argsize = sizeof(struct nlm4_lockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_res_wrapper),
+ .pc_xdrressize = NLM4_nlm4_res_sz,
+ .pc_name = "LOCK",
+ },
+ [NLMPROC4_CANCEL] = {
+ .pc_func = nlm4svc_proc_cancel,
+ .pc_decode = nlm4_svc_decode_nlm4_cancargs,
+ .pc_encode = nlm4_svc_encode_nlm4_res,
+ .pc_argsize = sizeof(struct nlm4_cancargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_res_wrapper),
+ .pc_xdrressize = NLM4_nlm4_res_sz,
+ .pc_name = "CANCEL",
+ },
+ [NLMPROC4_UNLOCK] = {
+ .pc_func = nlm4svc_proc_unlock,
+ .pc_decode = nlm4_svc_decode_nlm4_unlockargs,
+ .pc_encode = nlm4_svc_encode_nlm4_res,
+ .pc_argsize = sizeof(struct nlm4_unlockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_res_wrapper),
+ .pc_xdrressize = NLM4_nlm4_res_sz,
+ .pc_name = "UNLOCK",
+ },
+ [NLMPROC4_GRANTED] = {
+ .pc_func = nlm4svc_proc_granted,
+ .pc_decode = nlm4_svc_decode_nlm4_testargs,
+ .pc_encode = nlm4_svc_encode_nlm4_res,
+ .pc_argsize = sizeof(struct nlm4_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_res_wrapper),
+ .pc_xdrressize = NLM4_nlm4_res_sz,
+ .pc_name = "GRANTED",
+ },
+ [NLMPROC4_TEST_MSG] = {
+ .pc_func = nlm4svc_proc_test_msg,
+ .pc_decode = nlm4_svc_decode_nlm4_testargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "TEST_MSG",
+ },
+ [NLMPROC4_LOCK_MSG] = {
+ .pc_func = nlm4svc_proc_lock_msg,
+ .pc_decode = nlm4_svc_decode_nlm4_lockargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_lockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "LOCK_MSG",
+ },
+ [NLMPROC4_CANCEL_MSG] = {
+ .pc_func = nlm4svc_proc_cancel_msg,
+ .pc_decode = nlm4_svc_decode_nlm4_cancargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_cancargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "CANCEL_MSG",
+ },
+ [NLMPROC4_UNLOCK_MSG] = {
+ .pc_func = nlm4svc_proc_unlock_msg,
+ .pc_decode = nlm4_svc_decode_nlm4_unlockargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_unlockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNLOCK_MSG",
+ },
+ [NLMPROC4_GRANTED_MSG] = {
+ .pc_func = nlm4svc_proc_granted_msg,
+ .pc_decode = nlm4_svc_decode_nlm4_testargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "GRANTED_MSG",
+ },
+ [NLMPROC4_TEST_RES] = {
+ .pc_func = nlm4svc_proc_null,
+ .pc_decode = nlm4_svc_decode_nlm4_testres,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_testres),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "TEST_RES",
+ },
+ [NLMPROC4_LOCK_RES] = {
+ .pc_func = nlm4svc_proc_null,
+ .pc_decode = nlm4_svc_decode_nlm4_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_res),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "LOCK_RES",
+ },
+ [NLMPROC4_CANCEL_RES] = {
+ .pc_func = nlm4svc_proc_null,
+ .pc_decode = nlm4_svc_decode_nlm4_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_res),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "CANCEL_RES",
+ },
+ [NLMPROC4_UNLOCK_RES] = {
+ .pc_func = nlm4svc_proc_null,
+ .pc_decode = nlm4_svc_decode_nlm4_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_res),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNLOCK_RES",
+ },
+ [NLMPROC4_GRANTED_RES] = {
+ .pc_func = nlm4svc_proc_granted_res,
+ .pc_decode = nlm4_svc_decode_nlm4_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_res_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "GRANTED_RES",
+ },
+ [NLMPROC4_SM_NOTIFY] = {
+ .pc_func = nlm4svc_proc_sm_notify,
+ .pc_decode = nlm4_svc_decode_nlm4_notifyargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_notifyargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "SM_NOTIFY",
+ },
+ [17] = {
+ .pc_func = nlm4svc_proc_unused,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = 0,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNUSED",
+ },
+ [18] = {
+ .pc_func = nlm4svc_proc_unused,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = 0,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNUSED",
+ },
+ [19] = {
+ .pc_func = nlm4svc_proc_unused,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = 0,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNUSED",
+ },
+ [NLMPROC4_SHARE] = {
+ .pc_func = nlm4svc_proc_share,
+ .pc_decode = nlm4_svc_decode_nlm4_shareargs,
+ .pc_encode = nlm4_svc_encode_nlm4_shareres,
+ .pc_argsize = sizeof(struct nlm4_shareargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_shareres_wrapper),
+ .pc_xdrressize = NLM4_nlm4_shareres_sz,
+ .pc_name = "SHARE",
+ },
+ [NLMPROC4_UNSHARE] = {
+ .pc_func = nlm4svc_proc_unshare,
+ .pc_decode = nlm4_svc_decode_nlm4_shareargs,
+ .pc_encode = nlm4_svc_encode_nlm4_shareres,
+ .pc_argsize = sizeof(struct nlm4_shareargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_shareres_wrapper),
+ .pc_xdrressize = NLM4_nlm4_shareres_sz,
+ .pc_name = "UNSHARE",
+ },
+ [NLMPROC4_NM_LOCK] = {
+ .pc_func = nlm4svc_proc_nm_lock,
+ .pc_decode = nlm4_svc_decode_nlm4_lockargs,
+ .pc_encode = nlm4_svc_encode_nlm4_res,
+ .pc_argsize = sizeof(struct nlm4_lockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm4_res_wrapper),
+ .pc_xdrressize = NLM4_nlm4_res_sz,
+ .pc_name = "NM_LOCK",
+ },
+ [NLMPROC4_FREE_ALL] = {
+ .pc_func = nlm4svc_proc_free_all,
+ .pc_decode = nlm4_svc_decode_nlm4_notify,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm4_notify_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "FREE_ALL",
+ },
+};
+
+/*
+ * Storage requirements for XDR arguments and results
+ */
+union nlm4svc_xdrstore {
+ struct nlm4_testargs_wrapper testargs;
+ struct nlm4_lockargs_wrapper lockargs;
+ struct nlm4_cancargs_wrapper cancargs;
+ struct nlm4_unlockargs_wrapper unlockargs;
+ struct nlm4_notifyargs_wrapper notifyargs;
+ struct nlm4_shareargs_wrapper shareargs;
+ struct nlm4_notify_wrapper notify;
+ struct nlm4_testres_wrapper testres;
+ struct nlm4_res_wrapper res;
+ struct nlm4_shareres_wrapper shareres;
+};
+
+const struct svc_version nlmsvc_version4 = {
+ .vs_vers = 4,
+ .vs_nproc = ARRAY_SIZE(nlm4svc_procedures),
+ .vs_proc = nlm4svc_procedures,
+ .vs_dispatch = nlmsvc_dispatch,
+ .vs_xdrsize = sizeof(union nlm4svc_xdrstore),
+};
diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c
new file mode 100644
index 000000000..e628b5d35
--- /dev/null
+++ b/fs/lockd/svclock.c
@@ -0,0 +1,1042 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/svclock.c
+ *
+ * Handling of server-side locks, mostly of the blocked variety.
+ * This is the ugliest part of lockd because we tread on very thin ice.
+ * GRANT and CANCEL calls may get stuck, meet in mid-flight, etc.
+ * IMNSHO introducing the grant callback into the NLM protocol was one
+ * of the worst ideas Sun ever had. Except maybe for the idea of doing
+ * NFS file locking at all.
+ *
+ * I'm trying hard to avoid race conditions by protecting most accesses
+ * to a file's list of blocked locks through a semaphore. The global
+ * list of blocked locks is not protected in this fashion however.
+ * Therefore, some functions (such as the RPC callback for the async grant
+ * call) move blocked locks towards the head of the list *while some other
+ * process might be traversing it*. This should not be a problem in
+ * practice, because this will only cause functions traversing the list
+ * to visit some blocks twice.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc_xprt.h>
+
+#include "lockd.h"
+
+#define NLMDBG_FACILITY NLMDBG_SVCLOCK
+
+static void nlmsvc_release_block(struct nlm_block *block);
+static void nlmsvc_insert_block(struct nlm_block *block, unsigned long);
+static void nlmsvc_remove_block(struct nlm_block *block);
+
+static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct lockd_lock *lock);
+static void nlmsvc_freegrantargs(struct nlm_rqst *call);
+static const struct rpc_call_ops nlmsvc_grant_ops;
+
+/*
+ * The list of blocked locks to retry
+ */
+static LIST_HEAD(nlm_blocked);
+static DEFINE_SPINLOCK(nlm_blocked_lock);
+
+/*
+ * Insert a blocked lock into the global list
+ */
+static void
+nlmsvc_insert_block_locked(struct nlm_block *block, unsigned long when)
+{
+ struct nlm_block *b;
+ struct list_head *pos;
+
+ dprintk("lockd: nlmsvc_insert_block(%p, %ld)\n", block, when);
+ if (list_empty(&block->b_list)) {
+ kref_get(&block->b_count);
+ } else {
+ list_del_init(&block->b_list);
+ }
+
+ pos = &nlm_blocked;
+ if (when != NLM_NEVER) {
+ if ((when += jiffies) == NLM_NEVER)
+ when ++;
+ list_for_each(pos, &nlm_blocked) {
+ b = list_entry(pos, struct nlm_block, b_list);
+ if (time_after(b->b_when,when) || b->b_when == NLM_NEVER)
+ break;
+ }
+ /* On normal exit from the loop, pos == &nlm_blocked,
+ * so we will be adding to the end of the list - good
+ */
+ }
+
+ list_add_tail(&block->b_list, pos);
+ block->b_when = when;
+}
+
+static void nlmsvc_insert_block(struct nlm_block *block, unsigned long when)
+{
+ spin_lock(&nlm_blocked_lock);
+ nlmsvc_insert_block_locked(block, when);
+ spin_unlock(&nlm_blocked_lock);
+}
+
+/*
+ * Remove a block from the global list
+ */
+static inline void
+nlmsvc_remove_block(struct nlm_block *block)
+{
+ spin_lock(&nlm_blocked_lock);
+ if (!list_empty(&block->b_list)) {
+ list_del_init(&block->b_list);
+ spin_unlock(&nlm_blocked_lock);
+ nlmsvc_release_block(block);
+ return;
+ }
+ spin_unlock(&nlm_blocked_lock);
+}
+
+/*
+ * Find a block for a given lock
+ */
+static struct nlm_block *
+nlmsvc_lookup_block(struct nlm_file *file, struct lockd_lock *lock)
+{
+ struct nlm_block *block;
+ struct file_lock *fl;
+
+ dprintk("lockd: nlmsvc_lookup_block f=%p pd=%d %Ld-%Ld ty=%d\n",
+ file, lock->fl.c.flc_pid,
+ (long long)lock->fl.fl_start,
+ (long long)lock->fl.fl_end,
+ lock->fl.c.flc_type);
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ fl = &block->b_call->a_args.lock.fl;
+ dprintk("lockd: check f=%p pd=%d %Ld-%Ld ty=%d cookie=%*phN\n",
+ block->b_file, fl->c.flc_pid,
+ (long long)fl->fl_start,
+ (long long)fl->fl_end, fl->c.flc_type,
+ block->b_call->a_args.cookie.len,
+ block->b_call->a_args.cookie.data);
+ if (block->b_file == file && nlm_compare_locks(fl, &lock->fl)) {
+ kref_get(&block->b_count);
+ spin_unlock(&nlm_blocked_lock);
+ return block;
+ }
+ }
+ spin_unlock(&nlm_blocked_lock);
+
+ return NULL;
+}
+
+static int lockd_cookie_match(struct lockd_cookie *a, struct lockd_cookie *b)
+{
+ if (a->len != b->len)
+ return 0;
+ if (memcmp(a->data, b->data, a->len))
+ return 0;
+ return 1;
+}
+
+/*
+ * Find a block with a given NLM cookie.
+ */
+static inline struct nlm_block *
+nlmsvc_find_block(struct lockd_cookie *cookie)
+{
+ struct nlm_block *block;
+
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ if (lockd_cookie_match(&block->b_call->a_args.cookie, cookie))
+ goto found;
+ }
+ spin_unlock(&nlm_blocked_lock);
+
+ return NULL;
+
+found:
+ dprintk("nlmsvc_find_block(%*phN): block=%p\n",
+ cookie->len, cookie->data, block);
+ kref_get(&block->b_count);
+ spin_unlock(&nlm_blocked_lock);
+ return block;
+}
+
+/*
+ * Create a block and initialize it.
+ *
+ * Note: we explicitly set the cookie of the grant reply to that of
+ * the blocked lock request. The spec explicitly mentions that the client
+ * should _not_ rely on the callback containing the same cookie as the
+ * request, but (as I found out later) that's because some implementations
+ * do just this. Never mind the standards comittees, they support our
+ * logging industries.
+ *
+ * 10 years later: I hope we can safely ignore these old and broken
+ * clients by now. Let's fix this so we can uniquely identify an incoming
+ * GRANTED_RES message by cookie, without having to rely on the client's IP
+ * address. --okir
+ */
+static struct nlm_block *
+nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_host *host,
+ struct nlm_file *file, struct lockd_lock *lock,
+ struct lockd_cookie *cookie)
+{
+ struct nlm_block *block;
+ struct nlm_rqst *call = NULL;
+
+ call = nlm_alloc_call(host);
+ if (call == NULL)
+ return NULL;
+
+ /* Allocate memory for block, and initialize arguments */
+ block = kzalloc_obj(*block);
+ if (block == NULL)
+ goto failed;
+ kref_init(&block->b_count);
+ INIT_LIST_HEAD(&block->b_list);
+ INIT_LIST_HEAD(&block->b_flist);
+
+ if (!nlmsvc_setgrantargs(call, lock))
+ goto failed_free;
+
+ /* Set notifier function for VFS, and init args */
+ call->a_args.lock.fl.c.flc_flags |= FL_SLEEP;
+ call->a_args.lock.fl.fl_lmops = &nlmsvc_lock_operations;
+ nlmclnt_next_cookie(&call->a_args.cookie);
+
+ dprintk("lockd: created block %p...\n", block);
+
+ /* Create and initialize the block */
+ block->b_daemon = rqstp->rq_server;
+ block->b_host = host;
+ block->b_file = file;
+ file->f_count++;
+
+ /* Add to file's list of blocks */
+ list_add(&block->b_flist, &file->f_blocks);
+
+ /* Set up RPC arguments for callback */
+ block->b_call = call;
+ call->a_flags = RPC_TASK_ASYNC;
+ call->a_block = block;
+
+ return block;
+
+failed_free:
+ kfree(block);
+failed:
+ nlmsvc_release_call(call);
+ return NULL;
+}
+
+/*
+ * Delete a block.
+ * It is the caller's responsibility to check whether the file
+ * can be closed hereafter.
+ */
+static int nlmsvc_unlink_block(struct nlm_block *block)
+{
+ int status;
+ dprintk("lockd: unlinking block %p...\n", block);
+
+ /* Remove block from list */
+ status = locks_delete_block(&block->b_call->a_args.lock.fl);
+ nlmsvc_remove_block(block);
+ return status;
+}
+
+static void nlmsvc_free_block(struct kref *kref)
+{
+ struct nlm_block *block = container_of(kref, struct nlm_block, b_count);
+ struct nlm_file *file = block->b_file;
+
+ dprintk("lockd: freeing block %p...\n", block);
+
+ /* Remove block from file's list of blocks */
+ list_del_init(&block->b_flist);
+ mutex_unlock(&file->f_mutex);
+
+ nlmsvc_freegrantargs(block->b_call);
+ nlmsvc_release_call(block->b_call);
+ nlm_release_file(block->b_file);
+ kfree(block);
+}
+
+static void nlmsvc_release_block(struct nlm_block *block)
+{
+ if (block != NULL)
+ kref_put_mutex(&block->b_count, nlmsvc_free_block, &block->b_file->f_mutex);
+}
+
+/*
+ * Loop over all blocks and delete blocks held by
+ * a matching host.
+ */
+void nlmsvc_traverse_blocks(struct nlm_host *host,
+ struct nlm_file *file,
+ nlm_host_match_fn_t match)
+{
+ struct nlm_block *block, *next;
+
+restart:
+ mutex_lock(&file->f_mutex);
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry_safe(block, next, &file->f_blocks, b_flist) {
+ if (!match(block->b_host, host))
+ continue;
+ /* Do not destroy blocks that are not on
+ * the global retry list - why? */
+ if (list_empty(&block->b_list))
+ continue;
+ kref_get(&block->b_count);
+ spin_unlock(&nlm_blocked_lock);
+ mutex_unlock(&file->f_mutex);
+ nlmsvc_unlink_block(block);
+ nlmsvc_release_block(block);
+ goto restart;
+ }
+ spin_unlock(&nlm_blocked_lock);
+ mutex_unlock(&file->f_mutex);
+}
+
+static struct nlm_lockowner *
+nlmsvc_get_lockowner(struct nlm_lockowner *lockowner)
+{
+ refcount_inc(&lockowner->count);
+ return lockowner;
+}
+
+void nlmsvc_put_lockowner(struct nlm_lockowner *lockowner)
+{
+ if (!refcount_dec_and_lock(&lockowner->count, &lockowner->host->h_lock))
+ return;
+ list_del(&lockowner->list);
+ spin_unlock(&lockowner->host->h_lock);
+ nlmsvc_release_host(lockowner->host);
+ kfree(lockowner);
+}
+
+static struct nlm_lockowner *__nlmsvc_find_lockowner(struct nlm_host *host, pid_t pid)
+{
+ struct nlm_lockowner *lockowner;
+ list_for_each_entry(lockowner, &host->h_lockowners, list) {
+ if (lockowner->pid != pid)
+ continue;
+ return nlmsvc_get_lockowner(lockowner);
+ }
+ return NULL;
+}
+
+static struct nlm_lockowner *nlmsvc_find_lockowner(struct nlm_host *host, pid_t pid)
+{
+ struct nlm_lockowner *res, *new = NULL;
+
+ spin_lock(&host->h_lock);
+ res = __nlmsvc_find_lockowner(host, pid);
+
+ if (res == NULL) {
+ spin_unlock(&host->h_lock);
+ new = kmalloc_obj(*res);
+ spin_lock(&host->h_lock);
+ res = __nlmsvc_find_lockowner(host, pid);
+ if (res == NULL && new != NULL) {
+ res = new;
+ /* fs/locks.c will manage the refcount through lock_ops */
+ refcount_set(&new->count, 1);
+ new->pid = pid;
+ new->host = nlm_get_host(host);
+ list_add(&new->list, &host->h_lockowners);
+ new = NULL;
+ }
+ }
+
+ spin_unlock(&host->h_lock);
+ kfree(new);
+ return res;
+}
+
+void
+nlmsvc_release_lockowner(struct lockd_lock *lock)
+{
+ if (lock->fl.c.flc_owner)
+ nlmsvc_put_lockowner(lock->fl.c.flc_owner);
+}
+
+void nlmsvc_locks_init_private(struct file_lock *fl, struct nlm_host *host,
+ pid_t pid)
+{
+ fl->c.flc_owner = nlmsvc_find_lockowner(host, pid);
+}
+
+/*
+ * Initialize arguments for GRANTED call. The nlm_rqst structure
+ * has been cleared already.
+ */
+static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct lockd_lock *lock)
+{
+ locks_copy_lock(&call->a_args.lock.fl, &lock->fl);
+ memcpy(&call->a_args.lock.fh, &lock->fh, sizeof(call->a_args.lock.fh));
+ call->a_args.lock.caller = utsname()->nodename;
+ call->a_args.lock.oh.len = lock->oh.len;
+
+ /* set default data area */
+ call->a_args.lock.oh.data = call->a_owner;
+ call->a_args.lock.svid = ((struct nlm_lockowner *) lock->fl.c.flc_owner)->pid;
+
+ if (lock->oh.len > NLMCLNT_OHSIZE) {
+ void *data = kmalloc(lock->oh.len, GFP_KERNEL);
+ if (!data)
+ return 0;
+ call->a_args.lock.oh.data = (u8 *) data;
+ }
+
+ memcpy(call->a_args.lock.oh.data, lock->oh.data, lock->oh.len);
+ return 1;
+}
+
+static void nlmsvc_freegrantargs(struct nlm_rqst *call)
+{
+ if (call->a_args.lock.oh.data != call->a_owner)
+ kfree(call->a_args.lock.oh.data);
+
+ locks_release_private(&call->a_args.lock.fl);
+}
+
+/*
+ * Deferred lock request handling for non-blocking lock
+ */
+static __be32
+nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block)
+{
+ __be32 status = nlm_lck_denied_nolocks;
+
+ block->b_flags |= B_QUEUED;
+
+ nlmsvc_insert_block(block, NLM_TIMEOUT);
+
+ block->b_cache_req = &rqstp->rq_chandle;
+ if (rqstp->rq_chandle.defer) {
+ block->b_deferred_req =
+ rqstp->rq_chandle.defer(block->b_cache_req);
+ if (block->b_deferred_req != NULL)
+ status = nlm__int__drop_reply;
+ }
+ dprintk("lockd: nlmsvc_defer_lock_rqst block %p flags %d status %d\n",
+ block, block->b_flags, ntohl(status));
+
+ return status;
+}
+
+/*
+ * Attempt to establish a lock, and if it can't be granted, block it
+ * if required.
+ */
+__be32
+nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file,
+ struct nlm_host *host, struct lockd_lock *lock, int wait,
+ struct lockd_cookie *cookie, int reclaim)
+{
+ struct inode *inode __maybe_unused = nlmsvc_file_inode(file);
+ struct nlm_block *block = NULL;
+ int error;
+ int mode;
+ int async_block = 0;
+ __be32 ret;
+
+ dprintk("lockd: nlmsvc_lock(%s/%llu, ty=%d, pi=%d, %Ld-%Ld, bl=%d)\n",
+ inode->i_sb->s_id, inode->i_ino,
+ lock->fl.c.flc_type,
+ lock->fl.c.flc_pid,
+ (long long)lock->fl.fl_start,
+ (long long)lock->fl.fl_end,
+ wait);
+
+ if (nlmsvc_file_cannot_lock(file))
+ return nlm_lck_denied_nolocks;
+
+ if (!locks_can_async_lock(nlmsvc_file_file(file)->f_op)) {
+ async_block = wait;
+ wait = 0;
+ }
+
+ /* Lock file against concurrent access */
+ mutex_lock(&file->f_mutex);
+ /* Get existing block (in case client is busy-waiting)
+ * or create new block
+ */
+ block = nlmsvc_lookup_block(file, lock);
+ if (block == NULL) {
+ block = nlmsvc_create_block(rqstp, host, file, lock, cookie);
+ ret = nlm_lck_denied_nolocks;
+ if (block == NULL)
+ goto out;
+ lock = &block->b_call->a_args.lock;
+ } else
+ lock->fl.c.flc_flags &= ~FL_SLEEP;
+
+ if (block->b_flags & B_QUEUED) {
+ dprintk("lockd: nlmsvc_lock deferred block %p flags %d\n",
+ block, block->b_flags);
+ if (block->b_granted) {
+ nlmsvc_unlink_block(block);
+ ret = nlm_granted;
+ goto out;
+ }
+ if (block->b_flags & B_TIMED_OUT) {
+ nlmsvc_unlink_block(block);
+ ret = nlm_lck_denied;
+ goto out;
+ }
+ ret = nlm__int__drop_reply;
+ goto out;
+ }
+
+ if (locks_in_grace(SVC_NET(rqstp)) && !reclaim) {
+ ret = nlm_lck_denied_grace_period;
+ goto out;
+ }
+ if (reclaim && !locks_in_grace(SVC_NET(rqstp))) {
+ ret = nlm_lck_denied_grace_period;
+ goto out;
+ }
+
+ spin_lock(&nlm_blocked_lock);
+ /*
+ * If this is a lock request for an already pending
+ * lock request we return nlm_lck_blocked without calling
+ * vfs_lock_file() again. Otherwise we have two pending
+ * requests on the underlaying ->lock() implementation but
+ * only one nlm_block to being granted by lm_grant().
+ */
+ if (locks_can_async_lock(nlmsvc_file_file(file)->f_op) &&
+ !list_empty(&block->b_list)) {
+ spin_unlock(&nlm_blocked_lock);
+ ret = nlm_lck_blocked;
+ goto out;
+ }
+
+ /* Append to list of blocked */
+ nlmsvc_insert_block_locked(block, NLM_NEVER);
+ spin_unlock(&nlm_blocked_lock);
+
+ if (!wait)
+ lock->fl.c.flc_flags &= ~FL_SLEEP;
+ mode = lock_to_openmode(&lock->fl);
+ error = vfs_lock_file(file->f_file[mode], F_SETLK, &lock->fl, NULL);
+ lock->fl.c.flc_flags &= ~FL_SLEEP;
+
+ dprintk("lockd: vfs_lock_file returned %d\n", error);
+ switch (error) {
+ case 0:
+ nlmsvc_remove_block(block);
+ ret = nlm_granted;
+ goto out;
+ case -EAGAIN:
+ if (!wait)
+ nlmsvc_remove_block(block);
+ ret = async_block ? nlm_lck_blocked : nlm_lck_denied;
+ goto out;
+ case FILE_LOCK_DEFERRED:
+ if (wait)
+ break;
+ /* Filesystem lock operation is in progress
+ Add it to the queue waiting for callback */
+ ret = nlmsvc_defer_lock_rqst(rqstp, block);
+ goto out;
+ case -EDEADLK:
+ nlmsvc_remove_block(block);
+ ret = nlm__int__deadlock;
+ goto out;
+ default: /* includes ENOLCK */
+ nlmsvc_remove_block(block);
+ ret = nlm_lck_denied_nolocks;
+ goto out;
+ }
+
+ ret = nlm_lck_blocked;
+out:
+ mutex_unlock(&file->f_mutex);
+ nlmsvc_release_block(block);
+ dprintk("lockd: nlmsvc_lock returned %u\n", ret);
+ return ret;
+}
+
+/*
+ * Test for presence of a conflicting lock.
+ */
+__be32
+nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
+ struct nlm_host *host, struct lockd_lock *lock,
+ struct lockd_lock *conflock)
+{
+ int error;
+ __be32 ret;
+
+ dprintk("lockd: nlmsvc_testlock(%s/%llu, ty=%d, %Ld-%Ld)\n",
+ nlmsvc_file_inode(file)->i_sb->s_id,
+ nlmsvc_file_inode(file)->i_ino,
+ lock->fl.c.flc_type,
+ (long long)lock->fl.fl_start,
+ (long long)lock->fl.fl_end);
+
+ if (nlmsvc_file_cannot_lock(file))
+ return nlm_lck_denied_nolocks;
+
+ if (locks_in_grace(SVC_NET(rqstp))) {
+ ret = nlm_lck_denied_grace_period;
+ goto out;
+ }
+
+ locks_init_lock(&conflock->fl);
+ /* vfs_test_lock only uses start, end, and owner, but tests flc_file */
+ conflock->fl.c.flc_file = lock->fl.c.flc_file;
+ conflock->fl.fl_start = lock->fl.fl_start;
+ conflock->fl.fl_end = lock->fl.fl_end;
+ conflock->fl.c.flc_owner = lock->fl.c.flc_owner;
+ error = vfs_test_lock(lock->fl.c.flc_file, &conflock->fl);
+ if (error) {
+ ret = nlm_lck_denied_nolocks;
+ goto out;
+ }
+
+ if (conflock->fl.c.flc_type == F_UNLCK) {
+ ret = nlm_granted;
+ goto out;
+ }
+
+ dprintk("lockd: conflicting lock(ty=%d, %Ld-%Ld)\n",
+ conflock->fl.c.flc_type, (long long)conflock->fl.fl_start,
+ (long long)conflock->fl.fl_end);
+ conflock->caller = "somehost"; /* FIXME */
+ conflock->len = strlen(conflock->caller);
+ conflock->oh.len = 0; /* don't return OH info */
+ conflock->svid = conflock->fl.c.flc_pid;
+ locks_release_private(&conflock->fl);
+
+ ret = nlm_lck_denied;
+out:
+ return ret;
+}
+
+/*
+ * Remove a lock.
+ * This implies a CANCEL call: We send a GRANT_MSG, the client replies
+ * with a GRANT_RES call which gets lost, and calls UNLOCK immediately
+ * afterwards. In this case the block will still be there, and hence
+ * must be removed.
+ */
+__be32
+nlmsvc_unlock(struct net *net, struct nlm_file *file, struct lockd_lock *lock)
+{
+ int error = 0;
+
+ dprintk("lockd: nlmsvc_unlock(%s/%llu, pi=%d, %Ld-%Ld)\n",
+ nlmsvc_file_inode(file)->i_sb->s_id,
+ nlmsvc_file_inode(file)->i_ino,
+ lock->fl.c.flc_pid,
+ (long long)lock->fl.fl_start,
+ (long long)lock->fl.fl_end);
+
+ if (nlmsvc_file_cannot_lock(file))
+ return nlm_lck_denied_nolocks;
+
+ /* First, cancel any lock that might be there */
+ nlmsvc_cancel_blocked(net, file, lock);
+
+ lock->fl.c.flc_type = F_UNLCK;
+ lock->fl.c.flc_file = file->f_file[O_RDONLY];
+ if (lock->fl.c.flc_file)
+ error = vfs_lock_file(lock->fl.c.flc_file, F_SETLK,
+ &lock->fl, NULL);
+ lock->fl.c.flc_file = file->f_file[O_WRONLY];
+ if (lock->fl.c.flc_file)
+ error |= vfs_lock_file(lock->fl.c.flc_file, F_SETLK,
+ &lock->fl, NULL);
+
+ return (error < 0)? nlm_lck_denied_nolocks : nlm_granted;
+}
+
+/*
+ * Cancel a previously blocked request.
+ *
+ * A cancel request always overrides any grant that may currently
+ * be in progress.
+ * The calling procedure must check whether the file can be closed.
+ */
+__be32
+nlmsvc_cancel_blocked(struct net *net, struct nlm_file *file, struct lockd_lock *lock)
+{
+ struct nlm_block *block;
+ int status = 0;
+ int mode;
+
+ dprintk("lockd: nlmsvc_cancel(%s/%llu, pi=%d, %Ld-%Ld)\n",
+ nlmsvc_file_inode(file)->i_sb->s_id,
+ nlmsvc_file_inode(file)->i_ino,
+ lock->fl.c.flc_pid,
+ (long long)lock->fl.fl_start,
+ (long long)lock->fl.fl_end);
+
+ if (nlmsvc_file_cannot_lock(file))
+ return nlm_lck_denied_nolocks;
+
+ if (locks_in_grace(net))
+ return nlm_lck_denied_grace_period;
+
+ mutex_lock(&file->f_mutex);
+ block = nlmsvc_lookup_block(file, lock);
+ mutex_unlock(&file->f_mutex);
+ if (block != NULL) {
+ struct file_lock *fl = &block->b_call->a_args.lock.fl;
+
+ mode = lock_to_openmode(fl);
+ vfs_cancel_lock(block->b_file->f_file[mode], fl);
+ status = nlmsvc_unlink_block(block);
+ nlmsvc_release_block(block);
+ }
+ return status ? nlm_lck_denied : nlm_granted;
+}
+
+/*
+ * This is a callback from the filesystem for VFS file lock requests.
+ * It will be used if lm_grant is defined and the filesystem can not
+ * respond to the request immediately.
+ * For SETLK or SETLKW request it will get the local posix lock.
+ * In all cases it will move the block to the head of nlm_blocked q where
+ * nlmsvc_retry_blocked() can send back a reply for SETLKW or revisit the
+ * deferred rpc for GETLK and SETLK.
+ */
+static void
+nlmsvc_update_deferred_block(struct nlm_block *block, int result)
+{
+ block->b_flags |= B_GOT_CALLBACK;
+ if (result == 0)
+ block->b_granted = 1;
+ else
+ block->b_flags |= B_TIMED_OUT;
+}
+
+static int nlmsvc_grant_deferred(struct file_lock *fl, int result)
+{
+ struct nlm_block *block;
+ int rc = -ENOENT;
+
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ if (nlm_compare_locks(&block->b_call->a_args.lock.fl, fl)) {
+ dprintk("lockd: nlmsvc_notify_blocked block %p flags %d\n",
+ block, block->b_flags);
+ if (block->b_flags & B_QUEUED) {
+ if (block->b_flags & B_TIMED_OUT) {
+ rc = -ENOLCK;
+ break;
+ }
+ nlmsvc_update_deferred_block(block, result);
+ } else if (result == 0)
+ block->b_granted = 1;
+
+ nlmsvc_insert_block_locked(block, 0);
+ svc_wake_up(block->b_daemon);
+ rc = 0;
+ break;
+ }
+ }
+ spin_unlock(&nlm_blocked_lock);
+ if (rc == -ENOENT)
+ printk(KERN_WARNING "lockd: grant for unknown block\n");
+ return rc;
+}
+
+/*
+ * Unblock a blocked lock request. This is a callback invoked from the
+ * VFS layer when a lock on which we blocked is removed.
+ *
+ * This function doesn't grant the blocked lock instantly, but rather moves
+ * the block to the head of nlm_blocked where it can be picked up by lockd.
+ */
+static void
+nlmsvc_notify_blocked(struct file_lock *fl)
+{
+ struct nlm_block *block;
+
+ dprintk("lockd: VFS unblock notification for block %p\n", fl);
+ spin_lock(&nlm_blocked_lock);
+ list_for_each_entry(block, &nlm_blocked, b_list) {
+ if (nlm_compare_locks(&block->b_call->a_args.lock.fl, fl)) {
+ nlmsvc_insert_block_locked(block, 0);
+ spin_unlock(&nlm_blocked_lock);
+ svc_wake_up(block->b_daemon);
+ return;
+ }
+ }
+ spin_unlock(&nlm_blocked_lock);
+ printk(KERN_WARNING "lockd: notification for unknown block!\n");
+}
+
+static fl_owner_t nlmsvc_get_owner(fl_owner_t owner)
+{
+ return nlmsvc_get_lockowner(owner);
+}
+
+static void nlmsvc_put_owner(fl_owner_t owner)
+{
+ nlmsvc_put_lockowner(owner);
+}
+
+const struct lock_manager_operations nlmsvc_lock_operations = {
+ .lm_notify = nlmsvc_notify_blocked,
+ .lm_grant = nlmsvc_grant_deferred,
+ .lm_get_owner = nlmsvc_get_owner,
+ .lm_put_owner = nlmsvc_put_owner,
+};
+
+/*
+ * Try to claim a lock that was previously blocked.
+ *
+ * Note that we use both the RPC_GRANTED_MSG call _and_ an async
+ * RPC thread when notifying the client. This seems like overkill...
+ * Here's why:
+ * - we don't want to use a synchronous RPC thread, otherwise
+ * we might find ourselves hanging on a dead portmapper.
+ * - Some lockd implementations (e.g. HP) don't react to
+ * RPC_GRANTED calls; they seem to insist on RPC_GRANTED_MSG calls.
+ */
+static void
+nlmsvc_grant_blocked(struct nlm_block *block)
+{
+ struct nlm_file *file = block->b_file;
+ struct lockd_lock *lock = &block->b_call->a_args.lock;
+ int mode;
+ int error;
+ loff_t fl_start, fl_end;
+
+ dprintk("lockd: grant blocked lock %p\n", block);
+
+ kref_get(&block->b_count);
+
+ /* Unlink block request from list */
+ nlmsvc_unlink_block(block);
+
+ /* If b_granted is true this means we've been here before.
+ * Just retry the grant callback, possibly refreshing the RPC
+ * binding */
+ if (block->b_granted) {
+ nlm_rebind_host(block->b_host);
+ goto callback;
+ }
+
+ /* Try the lock operation again */
+ /* vfs_lock_file() can mangle fl_start and fl_end, but we need
+ * them unchanged for the GRANT_MSG
+ */
+ lock->fl.c.flc_flags |= FL_SLEEP;
+ fl_start = lock->fl.fl_start;
+ fl_end = lock->fl.fl_end;
+ mode = lock_to_openmode(&lock->fl);
+ error = vfs_lock_file(file->f_file[mode], F_SETLK, &lock->fl, NULL);
+ lock->fl.c.flc_flags &= ~FL_SLEEP;
+ lock->fl.fl_start = fl_start;
+ lock->fl.fl_end = fl_end;
+
+ switch (error) {
+ case 0:
+ break;
+ case FILE_LOCK_DEFERRED:
+ dprintk("lockd: lock still blocked error %d\n", error);
+ nlmsvc_insert_block(block, NLM_NEVER);
+ nlmsvc_release_block(block);
+ return;
+ default:
+ printk(KERN_WARNING "lockd: unexpected error %d in %s!\n",
+ -error, __func__);
+ nlmsvc_insert_block(block, 10 * HZ);
+ nlmsvc_release_block(block);
+ return;
+ }
+
+callback:
+ /* Lock was granted by VFS. */
+ dprintk("lockd: GRANTing blocked lock.\n");
+ block->b_granted = 1;
+
+ /* keep block on the list, but don't reattempt until the RPC
+ * completes or the submission fails
+ */
+ nlmsvc_insert_block(block, NLM_NEVER);
+
+ /* Call the client -- use a soft RPC task since nlmsvc_retry_blocked
+ * will queue up a new one if this one times out
+ */
+ error = nlm_async_call(block->b_call, NLMPROC_GRANTED_MSG,
+ &nlmsvc_grant_ops);
+
+ /* RPC submission failed, wait a bit and retry */
+ if (error < 0)
+ nlmsvc_insert_block(block, 10 * HZ);
+}
+
+/*
+ * This is the callback from the RPC layer when the NLM_GRANTED_MSG
+ * RPC call has succeeded or timed out.
+ * Like all RPC callbacks, it is invoked by the rpciod process, so it
+ * better not sleep. Therefore, we put the blocked lock on the nlm_blocked
+ * chain once more in order to have it removed by lockd itself (which can
+ * then sleep on the file semaphore without disrupting e.g. the nfs client).
+ */
+static void nlmsvc_grant_callback(struct rpc_task *task, void *data)
+{
+ struct nlm_rqst *call = data;
+ struct nlm_block *block = call->a_block;
+ unsigned long timeout;
+
+ dprintk("lockd: GRANT_MSG RPC callback\n");
+
+ spin_lock(&nlm_blocked_lock);
+ /* if the block is not on a list at this point then it has
+ * been invalidated. Don't try to requeue it.
+ *
+ * FIXME: it's possible that the block is removed from the list
+ * after this check but before the nlmsvc_insert_block. In that
+ * case it will be added back. Perhaps we need better locking
+ * for nlm_blocked?
+ */
+ if (list_empty(&block->b_list))
+ goto out;
+
+ /* Technically, we should down the file semaphore here. Since we
+ * move the block towards the head of the queue only, no harm
+ * can be done, though. */
+ if (task->tk_status < 0) {
+ /* RPC error: Re-insert for retransmission */
+ timeout = 10 * HZ;
+ } else {
+ /* Call was successful, now wait for client callback */
+ timeout = 60 * HZ;
+ }
+ nlmsvc_insert_block_locked(block, timeout);
+ svc_wake_up(block->b_daemon);
+out:
+ spin_unlock(&nlm_blocked_lock);
+}
+
+/*
+ * FIXME: nlmsvc_release_block() grabs a mutex. This is not allowed for an
+ * .rpc_release rpc_call_op
+ */
+static void nlmsvc_grant_release(void *data)
+{
+ struct nlm_rqst *call = data;
+ nlmsvc_release_block(call->a_block);
+}
+
+static const struct rpc_call_ops nlmsvc_grant_ops = {
+ .rpc_call_done = nlmsvc_grant_callback,
+ .rpc_release = nlmsvc_grant_release,
+};
+
+/*
+ * We received a GRANT_RES callback. Try to find the corresponding
+ * block.
+ */
+void
+nlmsvc_grant_reply(struct lockd_cookie *cookie, __be32 status)
+{
+ struct nlm_block *block;
+ struct file_lock *fl;
+ int error;
+
+ dprintk("grant_reply: looking for cookie %x, s=%d\n",
+ *(unsigned int *)(cookie->data), status);
+ if (!(block = nlmsvc_find_block(cookie)))
+ return;
+
+ switch (status) {
+ case nlm_lck_denied_grace_period:
+ /* Try again in a couple of seconds */
+ nlmsvc_insert_block(block, 10 * HZ);
+ break;
+ case nlm_lck_denied:
+ /* Client doesn't want it, just unlock it */
+ nlmsvc_unlink_block(block);
+ fl = &block->b_call->a_args.lock.fl;
+ fl->c.flc_type = F_UNLCK;
+ error = vfs_lock_file(fl->c.flc_file, F_SETLK, fl, NULL);
+ if (error)
+ pr_warn("lockd: unable to unlock lock rejected by client!\n");
+ break;
+ default:
+ /*
+ * Either it was accepted or the status makes no sense
+ * just unlink it either way.
+ */
+ nlmsvc_unlink_block(block);
+ }
+ nlmsvc_release_block(block);
+}
+
+/* Helper function to handle retry of a deferred block.
+ * If it is a blocking lock, call grant_blocked.
+ * For a non-blocking lock or test lock, revisit the request.
+ */
+static void
+retry_deferred_block(struct nlm_block *block)
+{
+ if (!(block->b_flags & B_GOT_CALLBACK))
+ block->b_flags |= B_TIMED_OUT;
+ nlmsvc_insert_block(block, NLM_TIMEOUT);
+ dprintk("revisit block %p flags %d\n", block, block->b_flags);
+ if (block->b_deferred_req) {
+ block->b_deferred_req->revisit(block->b_deferred_req, 0);
+ block->b_deferred_req = NULL;
+ }
+}
+
+/*
+ * Retry all blocked locks that have been notified. This is where lockd
+ * picks up locks that can be granted, or grant notifications that must
+ * be retransmitted.
+ */
+void
+nlmsvc_retry_blocked(struct svc_rqst *rqstp)
+{
+ unsigned long timeout = MAX_SCHEDULE_TIMEOUT;
+ struct nlm_block *block;
+
+ spin_lock(&nlm_blocked_lock);
+ while (!list_empty(&nlm_blocked) && !svc_thread_should_stop(rqstp)) {
+ block = list_entry(nlm_blocked.next, struct nlm_block, b_list);
+
+ if (block->b_when == NLM_NEVER)
+ break;
+ if (time_after(block->b_when, jiffies)) {
+ timeout = block->b_when - jiffies;
+ break;
+ }
+ spin_unlock(&nlm_blocked_lock);
+
+ dprintk("nlmsvc_retry_blocked(%p, when=%ld)\n",
+ block, block->b_when);
+ if (block->b_flags & B_QUEUED) {
+ dprintk("nlmsvc_retry_blocked delete block (%p, granted=%d, flags=%d)\n",
+ block, block->b_granted, block->b_flags);
+ retry_deferred_block(block);
+ } else
+ nlmsvc_grant_blocked(block);
+ spin_lock(&nlm_blocked_lock);
+ }
+ spin_unlock(&nlm_blocked_lock);
+
+ if (timeout < MAX_SCHEDULE_TIMEOUT)
+ mod_timer(&nlmsvc_retry, jiffies + timeout);
+}
diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c
new file mode 100644
index 000000000..a8b5195c2
--- /dev/null
+++ b/fs/lockd/svcproc.c
@@ -0,0 +1,1453 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/svcproc.c
+ *
+ * Lockd server procedures. We don't implement the NLM_*_RES
+ * procedures because we don't use the async procedures.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/time.h>
+#include <linux/sunrpc/svc_xprt.h>
+
+#include "lockd.h"
+
+/*
+ * xdr.h defines SM_PRIV_SIZE as a macro. nlm3xdr_gen.h defines
+ * it as an enum constant. Undefine the macro before including
+ * the generated header.
+ */
+#undef SM_PRIV_SIZE
+
+#include "share.h"
+#include "nlm3xdr_gen.h"
+
+/*
+ * Size of an NFSv2 file handle, in bytes, which is 32.
+ * Defined locally to avoid including uapi/linux/nfs2.h.
+ */
+#define NLM3_FHSIZE 32
+
+/*
+ * Wrapper structures combine xdrgen types with legacy lockd_lock.
+ * The xdrgen field must be first so the structure can be cast
+ * to its XDR type for the RPC dispatch layer.
+ */
+struct nlm_testargs_wrapper {
+ struct nlm_testargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm_testargs_wrapper, xdrgen) == 0);
+
+struct nlm_testres_wrapper {
+ struct nlm_testres xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm_testres_wrapper, xdrgen) == 0);
+
+struct nlm_lockargs_wrapper {
+ struct nlm_lockargs xdrgen;
+ struct lockd_cookie cookie;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm_lockargs_wrapper, xdrgen) == 0);
+
+struct nlm_res_wrapper {
+ struct nlm_res xdrgen;
+ struct lockd_cookie cookie;
+};
+
+static_assert(offsetof(struct nlm_res_wrapper, xdrgen) == 0);
+
+struct nlm_cancargs_wrapper {
+ struct nlm_cancargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm_cancargs_wrapper, xdrgen) == 0);
+
+struct nlm_unlockargs_wrapper {
+ struct nlm_unlockargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm_unlockargs_wrapper, xdrgen) == 0);
+
+struct nlm_notifyargs_wrapper {
+ struct nlm_notifyargs xdrgen;
+ struct lockd_reboot reboot;
+};
+
+static_assert(offsetof(struct nlm_notifyargs_wrapper, xdrgen) == 0);
+
+struct nlm_shareargs_wrapper {
+ struct nlm_shareargs xdrgen;
+ struct lockd_lock lock;
+};
+
+static_assert(offsetof(struct nlm_shareargs_wrapper, xdrgen) == 0);
+
+struct nlm_shareres_wrapper {
+ struct nlm_shareres xdrgen;
+};
+
+static_assert(offsetof(struct nlm_shareres_wrapper, xdrgen) == 0);
+
+struct nlm_notify_wrapper {
+ struct nlm_notify xdrgen;
+};
+
+static_assert(offsetof(struct nlm_notify_wrapper, xdrgen) == 0);
+
+static __be32
+nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object)
+{
+ if (object->len > NLM_MAXCOOKIELEN)
+ return nlm_lck_denied_nolocks;
+ cookie->len = object->len;
+ memcpy(cookie->data, object->data, object->len);
+ return nlm_granted;
+}
+
+static __be32
+nlm_lock_to_lockd_lock(struct lockd_lock *lock, struct nlm_lock *alock)
+{
+ if (alock->fh.len != NLM3_FHSIZE)
+ return nlm_lck_denied;
+ lock->fh.size = alock->fh.len;
+ memcpy(lock->fh.data, alock->fh.data, alock->fh.len);
+ lock->oh.len = alock->oh.len;
+ lock->oh.data = alock->oh.data;
+ lock->svid = alock->uppid;
+ lockd_set_file_lock_range3(&lock->fl, alock->l_offset, alock->l_len);
+ return nlm_granted;
+}
+
+static struct nlm_host *
+nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
+{
+ struct nlm_host *host;
+
+ if (!rcu_access_pointer(nlmsvc_ops))
+ return NULL;
+ host = nlmsvc_lookup_host(rqstp, caller.data, caller.len);
+ if (!host)
+ return NULL;
+ if (monitored && nsm_monitor(host) < 0) {
+ nlmsvc_release_host(host);
+ return NULL;
+ }
+ return host;
+}
+
+static __be32
+nlm3svc_lookup_file(struct svc_rqst *rqstp, struct nlm_host *host,
+ struct lockd_lock *lock, struct nlm_file **filp,
+ struct nlm_lock *xdr_lock, unsigned char type)
+{
+ bool is_test = (rqstp->rq_proc == NLMPROC_TEST ||
+ rqstp->rq_proc == NLMPROC_TEST_MSG);
+ struct file_lock *fl = &lock->fl;
+ struct nlm_file *file = NULL;
+ __be32 error;
+ int mode;
+
+ if (xdr_lock->fh.len != NLM3_FHSIZE)
+ return nlm_lck_denied_nolocks;
+ lock->fh.size = xdr_lock->fh.len;
+ memcpy(lock->fh.data, xdr_lock->fh.data, xdr_lock->fh.len);
+
+ lock->oh.len = xdr_lock->oh.len;
+ lock->oh.data = xdr_lock->oh.data;
+
+ lock->svid = xdr_lock->uppid;
+ lock->lock_start = xdr_lock->l_offset;
+ lock->lock_len = xdr_lock->l_len;
+
+ locks_init_lock(fl);
+ fl->c.flc_type = type;
+ lockd_set_file_lock_range3(fl, lock->lock_start, lock->lock_len);
+
+ mode = lock_to_openmode(fl);
+ if (is_test)
+ mode = O_RDWR;
+
+ error = nlm_lookup_file(rqstp, &file, lock, mode);
+ switch (error) {
+ case nlm_granted:
+ break;
+ case nlm__int__stale_fh:
+ case nlm__int__failed:
+ return nlm_lck_denied_nolocks;
+ default:
+ return error;
+ }
+ *filp = file;
+
+ fl->c.flc_flags = FL_POSIX;
+ if (is_test)
+ fl->c.flc_file = nlmsvc_file_file(file);
+ else
+ fl->c.flc_file = file->f_file[mode];
+ fl->c.flc_pid = current->tgid;
+ fl->fl_lmops = &nlmsvc_lock_operations;
+ nlmsvc_locks_init_private(fl, host, (pid_t)lock->svid);
+ if (!fl->c.flc_owner)
+ return nlm_lck_denied_nolocks;
+
+ return nlm_granted;
+}
+
+static inline __be32 cast_status(__be32 status)
+{
+ switch (status) {
+ case nlm_granted:
+ case nlm_lck_denied:
+ case nlm_lck_denied_nolocks:
+ case nlm_lck_blocked:
+ case nlm_lck_denied_grace_period:
+ case nlm__int__drop_reply:
+ break;
+ case nlm__int__deadlock:
+ status = nlm_lck_denied;
+ break;
+ default:
+ pr_warn_once("lockd: unhandled internal status %u\n",
+ be32_to_cpu(status));
+ status = nlm_lck_denied_nolocks;
+ break;
+ }
+ return status;
+}
+
+/**
+ * nlmsvc_proc_null - NULL: Test for presence of service
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void NLM_NULL(void) = 0;
+ */
+static __be32 nlmsvc_proc_null(struct svc_rqst *rqstp)
+{
+ return rpc_success;
+}
+
+/**
+ * nlmsvc_proc_test - TEST: Check for conflicting lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_testres NLM_TEST(nlm_testargs) = 1;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The server would be able to grant the
+ * requested lock.
+ * %LCK_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %LCK_DENIED_NOLOCKS: The server could not allocate the resources
+ * needed to process the request.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ */
+static __be32 nlmsvc_proc_test(struct svc_rqst *rqstp)
+{
+ struct nlm_testargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_testres_wrapper *resp = rqstp->rq_resp;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.test_stat.stat = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.test_stat.stat =
+ nlm3svc_lookup_file(rqstp, host, &argp->lock, &file,
+ &argp->xdrgen.alock, type);
+ if (resp->xdrgen.test_stat.stat)
+ goto out;
+
+ resp->xdrgen.test_stat.stat =
+ cast_status(nlmsvc_testlock(rqstp, file, host, &argp->lock,
+ &resp->lock));
+ nlmsvc_release_lockowner(&argp->lock);
+
+ if (resp->xdrgen.test_stat.stat == nlm_lck_denied) {
+ struct lockd_lock *conf = &resp->lock;
+ struct nlm_holder *holder = &resp->xdrgen.test_stat.u.holder;
+
+ holder->exclusive = (conf->fl.c.flc_type != F_RDLCK);
+ holder->uppid = conf->svid;
+ holder->oh.len = conf->oh.len;
+ holder->oh.data = conf->oh.data;
+ holder->l_offset = min_t(loff_t, conf->fl.fl_start,
+ NLM_OFFSET_MAX);
+ if (conf->fl.fl_end == OFFSET_MAX)
+ holder->l_len = 0;
+ else
+ holder->l_len = min_t(loff_t,
+ conf->fl.fl_end -
+ conf->fl.fl_start + 1,
+ NLM_OFFSET_MAX);
+ }
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.test_stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+static __be32
+nlmsvc_do_lock(struct svc_rqst *rqstp, bool monitored)
+{
+ struct nlm_lockargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_res_wrapper *resp = rqstp->rq_resp;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_netobj_to_cookie(&argp->cookie,
+ &argp->xdrgen.cookie);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name,
+ monitored);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ type);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = cast_status(nlmsvc_lock(rqstp, file, host,
+ &argp->lock,
+ argp->xdrgen.block,
+ &argp->cookie,
+ argp->xdrgen.reclaim));
+
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_lock - LOCK: Establish a monitored lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_res NLM_LOCK(nlm_lockargs) = 2;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The requested lock was granted.
+ * %LCK_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %LCK_DENIED_NOLOCKS: The server could not allocate the resources
+ * needed to process the request.
+ * %LCK_BLOCKED: The blocking request cannot be granted
+ * immediately. The server will send an
+ * NLM_GRANTED callback to the client when
+ * the lock can be granted.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ */
+static __be32
+nlmsvc_proc_lock(struct svc_rqst *rqstp)
+{
+ return nlmsvc_do_lock(rqstp, true);
+}
+
+/**
+ * nlmsvc_proc_cancel - CANCEL: Cancel an outstanding blocked lock request
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_res NLM_CANCEL(nlm_cancargs) = 3;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The requested lock was canceled.
+ * %LCK_DENIED: There was no lock to cancel.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated.
+ */
+static __be32
+nlmsvc_proc_cancel(struct svc_rqst *rqstp)
+{
+ struct nlm_cancargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_res_wrapper *resp = rqstp->rq_resp;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ type);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmsvc_cancel_blocked(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_unlock - UNLOCK: Remove a lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_res NLM_UNLOCK(nlm_unlockargs) = 4;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The requested lock was released.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated.
+ */
+static __be32
+nlmsvc_proc_unlock(struct svc_rqst *rqstp)
+{
+ struct nlm_unlockargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_res_wrapper *resp = rqstp->rq_resp;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock,
+ F_UNLCK);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmsvc_unlock(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_granted - GRANTED: Blocked lock has been granted
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ *
+ * RPC synopsis:
+ * nlm_res NLM_GRANTED(nlm_testargs) = 5;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The granted lock was accepted.
+ * %LCK_DENIED: The procedure failed, possibly due to
+ * internal resource constraints.
+ * %LCK_DENIED_GRACE_PERIOD: The client host recently restarted and
+ * its NLM is re-establishing existing locks,
+ * so it is not yet ready to accept callbacks.
+ */
+static __be32
+nlmsvc_proc_granted(struct svc_rqst *rqstp)
+{
+ struct nlm_testargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_res_wrapper *resp = rqstp->rq_resp;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat.stat = nlm_lock_to_lockd_lock(&argp->lock,
+ &argp->xdrgen.alock);
+ if (resp->xdrgen.stat.stat)
+ goto out;
+
+ resp->xdrgen.stat.stat = nlmclnt_grant(svc_addr(rqstp), &argp->lock);
+
+out:
+ return rpc_success;
+}
+
+/*
+ * This is the generic lockd callback for async RPC calls
+ */
+static void nlmsvc_callback_exit(struct rpc_task *task, void *data)
+{
+}
+
+void nlmsvc_release_call(struct nlm_rqst *call)
+{
+ if (!refcount_dec_and_test(&call->a_count))
+ return;
+ nlmsvc_release_host(call->a_host);
+ kfree(call);
+}
+
+static void nlmsvc_callback_release(void *data)
+{
+ nlmsvc_release_call(data);
+}
+
+static const struct rpc_call_ops nlmsvc_callback_ops = {
+ .rpc_call_done = nlmsvc_callback_exit,
+ .rpc_release = nlmsvc_callback_release,
+};
+
+/*
+ * `Async' versions of the above service routines. They aren't really,
+ * because we send the callback before the reply proper. I hope this
+ * doesn't break any clients.
+ */
+static __be32
+nlmsvc_callback(struct svc_rqst *rqstp, struct nlm_host *host, u32 proc,
+ __be32 (*func)(struct svc_rqst *, struct lockd_res *))
+{
+ struct nlm_rqst *call;
+ __be32 stat;
+
+ call = nlm_alloc_call(host);
+ nlmsvc_release_host(host);
+ if (call == NULL)
+ return rpc_system_err;
+
+ stat = func(rqstp, &call->a_res);
+ if (stat != 0) {
+ nlmsvc_release_call(call);
+ return stat;
+ }
+
+ call->a_flags = RPC_TASK_ASYNC;
+ if (nlm_async_reply(call, proc, &nlmsvc_callback_ops) < 0)
+ return rpc_system_err;
+ return rpc_success;
+}
+
+static __be32
+__nlmsvc_proc_test_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm_testargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_lockowner *owner;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, type);
+ if (resp->status)
+ goto out;
+
+ owner = argp->lock.fl.c.flc_owner;
+ resp->status = cast_status(nlmsvc_testlock(rqstp, file, host,
+ &argp->lock, &resp->lock));
+ nlmsvc_put_lockowner(owner);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_test_msg - TEST_MSG: Check for conflicting lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ * %rpc_garbage_args: The request arguments are malformed.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC_TEST_MSG(nlm_testargs) = 6;
+ *
+ * The response to this request is delivered via the TEST_RES procedure.
+ */
+static __be32 nlmsvc_proc_test_msg(struct svc_rqst *rqstp)
+{
+ struct nlm_testargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN)
+ return rpc_garbage_args;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlmsvc_callback(rqstp, host, NLMPROC_TEST_RES,
+ __nlmsvc_proc_test_msg);
+}
+
+static __be32
+__nlmsvc_proc_lock_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm_lockargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, true);
+ if (!host)
+ goto out;
+
+ resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, type);
+ if (resp->status)
+ goto out;
+
+ resp->status = cast_status(nlmsvc_lock(rqstp, file, host, &argp->lock,
+ argp->xdrgen.block, &resp->cookie,
+ argp->xdrgen.reclaim));
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_lock_msg - LOCK_MSG: Establish a monitored lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ * %rpc_garbage_args: The request arguments are malformed.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC_LOCK_MSG(nlm_lockargs) = 7;
+ *
+ * The response to this request is delivered via the LOCK_RES procedure.
+ */
+static __be32 nlmsvc_proc_lock_msg(struct svc_rqst *rqstp)
+{
+ struct nlm_lockargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN)
+ return rpc_garbage_args;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlmsvc_callback(rqstp, host, NLMPROC_LOCK_RES,
+ __nlmsvc_proc_lock_msg);
+}
+
+static __be32
+__nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm_cancargs_wrapper *argp = rqstp->rq_argp;
+ unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ resp->status = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->status = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, type);
+ if (resp->status)
+ goto out;
+
+ resp->status = nlmsvc_cancel_blocked(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_cancel_msg - CANCEL_MSG: Cancel an outstanding lock request
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ * %rpc_garbage_args: The request arguments are malformed.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC_CANCEL_MSG(nlm_cancargs) = 8;
+ *
+ * The response to this request is delivered via the CANCEL_RES procedure.
+ */
+static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp)
+{
+ struct nlm_cancargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN)
+ return rpc_garbage_args;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlmsvc_callback(rqstp, host, NLMPROC_CANCEL_RES,
+ __nlmsvc_proc_cancel_msg);
+}
+
+static __be32
+__nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm_unlockargs_wrapper *argp = rqstp->rq_argp;
+ struct net *net = SVC_NET(rqstp);
+ struct nlm_file *file = NULL;
+ struct nlm_host *host = NULL;
+
+ resp->status = nlm_lck_denied_nolocks;
+ if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ resp->status = nlm_lck_denied_grace_period;
+ if (locks_in_grace(net))
+ goto out;
+
+ resp->status = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock,
+ &file, &argp->xdrgen.alock, F_UNLCK);
+ if (resp->status)
+ goto out;
+
+ resp->status = nlmsvc_unlock(net, file, &argp->lock);
+ nlmsvc_release_lockowner(&argp->lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_unlock_msg - UNLOCK_MSG: Remove an existing lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ * %rpc_garbage_args: The request arguments are malformed.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC_UNLOCK_MSG(nlm_unlockargs) = 9;
+ *
+ * The response to this request is delivered via the UNLOCK_RES procedure.
+ */
+static __be32 nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp)
+{
+ struct nlm_unlockargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN)
+ return rpc_garbage_args;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ if (!host)
+ return rpc_system_err;
+
+ return nlmsvc_callback(rqstp, host, NLMPROC_UNLOCK_RES,
+ __nlmsvc_proc_unlock_msg);
+}
+
+static __be32
+__nlmsvc_proc_granted_msg(struct svc_rqst *rqstp, struct lockd_res *resp)
+{
+ struct nlm_testargs_wrapper *argp = rqstp->rq_argp;
+
+ resp->status = nlm_lck_denied;
+ if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie))
+ goto out;
+
+ if (nlm_lock_to_lockd_lock(&argp->lock, &argp->xdrgen.alock))
+ goto out;
+
+ resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock);
+
+out:
+ return rpc_success;
+}
+
+/**
+ * nlmsvc_proc_granted_msg - GRANTED_MSG: Blocked lock has been granted
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_garbage_args: The request arguments are malformed.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * RPC synopsis:
+ * void NLMPROC_GRANTED_MSG(nlm_testargs) = 10;
+ *
+ * The response to this request is delivered via the GRANTED_RES procedure.
+ */
+static __be32 nlmsvc_proc_granted_msg(struct svc_rqst *rqstp)
+{
+ struct nlm_testargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN)
+ return rpc_garbage_args;
+
+ host = nlmsvc_lookup_host(rqstp, argp->xdrgen.alock.caller_name.data,
+ argp->xdrgen.alock.caller_name.len);
+ if (!host)
+ return rpc_system_err;
+
+ return nlmsvc_callback(rqstp, host, NLMPROC_GRANTED_RES,
+ __nlmsvc_proc_granted_msg);
+}
+
+/**
+ * nlmsvc_proc_granted_res - GRANTED_RES: Lock Granted result
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ *
+ * RPC synopsis:
+ * void NLMPROC_GRANTED_RES(nlm_res) = 15;
+ */
+static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp)
+{
+ struct nlm_res_wrapper *argp = rqstp->rq_argp;
+
+ if (!rcu_access_pointer(nlmsvc_ops))
+ return rpc_success;
+
+ if (nlm_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie))
+ return rpc_success;
+ nlmsvc_grant_reply(&argp->cookie, argp->xdrgen.stat.stat);
+
+ return rpc_success;
+}
+
+/**
+ * nlmsvc_proc_sm_notify - SM_NOTIFY: Peer has rebooted
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_system_err: RPC execution failed.
+ *
+ * The SM_NOTIFY procedure is a private callback from Linux statd and is
+ * not part of the official NLM protocol.
+ *
+ * RPC synopsis:
+ * void NLM_SM_NOTIFY(nlm_notifyargs) = 16;
+ */
+static __be32 nlmsvc_proc_sm_notify(struct svc_rqst *rqstp)
+{
+ struct nlm_notifyargs_wrapper *argp = rqstp->rq_argp;
+ struct lockd_reboot *reboot = &argp->reboot;
+
+ if (!nlm_privileged_requester(rqstp)) {
+ char buf[RPC_MAX_ADDRBUFLEN];
+
+ pr_warn("lockd: rejected NSM callback from %s\n",
+ svc_print_addr(rqstp, buf, sizeof(buf)));
+ return rpc_system_err;
+ }
+
+ reboot->len = argp->xdrgen.notify.name.len;
+ reboot->mon = (char *)argp->xdrgen.notify.name.data;
+ reboot->state = argp->xdrgen.notify.state;
+ memcpy(&reboot->priv.data, argp->xdrgen.private,
+ sizeof(reboot->priv.data));
+
+ nlm_host_rebooted(SVC_NET(rqstp), reboot);
+
+ return rpc_success;
+}
+
+/**
+ * nlmsvc_proc_unused - stub for unused procedures
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_proc_unavail: Program can't support procedure.
+ */
+static __be32 nlmsvc_proc_unused(struct svc_rqst *rqstp)
+{
+ return rpc_proc_unavail;
+}
+
+/**
+ * nlmsvc_proc_share - SHARE: Open a file using DOS file-sharing modes
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_shareres NLM_SHARE(nlm_shareargs) = 20;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The requested share lock was granted.
+ * %LCK_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated.
+ */
+static __be32 nlmsvc_proc_share(struct svc_rqst *rqstp)
+{
+ struct nlm_shareargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_shareres_wrapper *resp = rqstp->rq_resp;
+ struct lockd_lock *lock = &argp->lock;
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+ struct nlm_lock xdr_lock = {
+ .fh = argp->xdrgen.share.fh,
+ .oh = argp->xdrgen.share.oh,
+ .uppid = LOCKD_SHARE_SVID,
+ };
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(SVC_NET(rqstp)) && !argp->xdrgen.reclaim)
+ goto out;
+
+ resp->xdrgen.stat = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.share.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat = nlm3svc_lookup_file(rqstp, host, lock, &file,
+ &xdr_lock, F_RDLCK);
+ if (resp->xdrgen.stat)
+ goto out;
+
+ resp->xdrgen.stat = nlmsvc_share_file(host, file, &lock->oh,
+ argp->xdrgen.share.access,
+ argp->xdrgen.share.mode);
+
+ nlmsvc_release_lockowner(lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_unshare - UNSHARE: Release a share reservation
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_shareres NLM_UNSHARE(nlm_shareargs) = 21;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The share reservation was released.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ *
+ * The Linux NLM server implementation also returns:
+ * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated.
+ */
+static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp)
+{
+ struct nlm_shareargs_wrapper *argp = rqstp->rq_argp;
+ struct nlm_shareres_wrapper *resp = rqstp->rq_resp;
+ struct lockd_lock *lock = &argp->lock;
+ struct nlm_lock xdr_lock = {
+ .fh = argp->xdrgen.share.fh,
+ .oh = argp->xdrgen.share.oh,
+ .uppid = LOCKD_SHARE_SVID,
+ };
+ struct nlm_host *host = NULL;
+ struct nlm_file *file = NULL;
+
+ resp->xdrgen.cookie = argp->xdrgen.cookie;
+
+ resp->xdrgen.stat = nlm_lck_denied_grace_period;
+ if (locks_in_grace(SVC_NET(rqstp)))
+ goto out;
+
+ resp->xdrgen.stat = nlm_lck_denied_nolocks;
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.share.caller_name, false);
+ if (!host)
+ goto out;
+
+ resp->xdrgen.stat = nlm3svc_lookup_file(rqstp, host, lock, &file,
+ &xdr_lock, F_RDLCK);
+ if (resp->xdrgen.stat)
+ goto out;
+
+ resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh,
+ argp->xdrgen.share.access,
+ argp->xdrgen.share.mode);
+
+ nlmsvc_release_lockowner(lock);
+
+out:
+ if (file)
+ nlm_release_file(file);
+ nlmsvc_release_host(host);
+ return resp->xdrgen.stat == nlm__int__drop_reply ?
+ rpc_drop_reply : rpc_success;
+}
+
+/**
+ * nlmsvc_proc_nm_lock - NM_LOCK: Establish a non-monitored lock
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ * %rpc_drop_reply: Do not send an RPC reply.
+ *
+ * RPC synopsis:
+ * nlm_res NLM_NM_LOCK(nlm_lockargs) = 22;
+ *
+ * Permissible procedure status codes:
+ * %LCK_GRANTED: The requested lock was granted.
+ * %LCK_DENIED: The requested lock conflicted with existing
+ * lock reservations for the file.
+ * %LCK_DENIED_NOLOCKS: The server could not allocate the resources
+ * needed to process the request.
+ * %LCK_BLOCKED: The blocking request cannot be granted
+ * immediately. The server will send an
+ * NLM_GRANTED callback to the client when
+ * the lock can be granted.
+ * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is
+ * re-establishing existing locks, and is not
+ * yet ready to accept normal service requests.
+ */
+static __be32 nlmsvc_proc_nm_lock(struct svc_rqst *rqstp)
+{
+ return nlmsvc_do_lock(rqstp, false);
+}
+
+/**
+ * nlmsvc_proc_free_all - FREE_ALL: Discard client's lock and share state
+ * @rqstp: RPC transaction context
+ *
+ * Returns:
+ * %rpc_success: RPC executed successfully.
+ *
+ * RPC synopsis:
+ * void NLMPROC_FREE_ALL(nlm_notify) = 23;
+ */
+static __be32 nlmsvc_proc_free_all(struct svc_rqst *rqstp)
+{
+ struct nlm_notify_wrapper *argp = rqstp->rq_argp;
+ struct nlm_host *host;
+
+ host = nlm3svc_lookup_host(rqstp, argp->xdrgen.name, false);
+ if (!host)
+ goto out;
+
+ nlmsvc_free_host_resources(host);
+
+ nlmsvc_release_host(host);
+
+out:
+ return rpc_success;
+}
+
+/*
+ * NLMv3 Server procedures.
+ */
+
+static const struct svc_procedure nlmsvc_procedures[24] = {
+ [NLM_NULL] = {
+ .pc_func = nlmsvc_proc_null,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = XDR_void,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "NULL",
+ },
+ [NLM_TEST] = {
+ .pc_func = nlmsvc_proc_test,
+ .pc_decode = nlm_svc_decode_nlm_testargs,
+ .pc_encode = nlm_svc_encode_nlm_testres,
+ .pc_argsize = sizeof(struct nlm_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_testres_wrapper),
+ .pc_xdrressize = NLM3_nlm_testres_sz,
+ .pc_name = "TEST",
+ },
+ [NLM_LOCK] = {
+ .pc_func = nlmsvc_proc_lock,
+ .pc_decode = nlm_svc_decode_nlm_lockargs,
+ .pc_encode = nlm_svc_encode_nlm_res,
+ .pc_argsize = sizeof(struct nlm_lockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_res_wrapper),
+ .pc_xdrressize = NLM3_nlm_res_sz,
+ .pc_name = "LOCK",
+ },
+ [NLM_CANCEL] = {
+ .pc_func = nlmsvc_proc_cancel,
+ .pc_decode = nlm_svc_decode_nlm_cancargs,
+ .pc_encode = nlm_svc_encode_nlm_res,
+ .pc_argsize = sizeof(struct nlm_cancargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_res_wrapper),
+ .pc_xdrressize = NLM3_nlm_res_sz,
+ .pc_name = "CANCEL",
+ },
+ [NLM_UNLOCK] = {
+ .pc_func = nlmsvc_proc_unlock,
+ .pc_decode = nlm_svc_decode_nlm_unlockargs,
+ .pc_encode = nlm_svc_encode_nlm_res,
+ .pc_argsize = sizeof(struct nlm_unlockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_res_wrapper),
+ .pc_xdrressize = NLM3_nlm_res_sz,
+ .pc_name = "UNLOCK",
+ },
+ [NLM_GRANTED] = {
+ .pc_func = nlmsvc_proc_granted,
+ .pc_decode = nlm_svc_decode_nlm_testargs,
+ .pc_encode = nlm_svc_encode_nlm_res,
+ .pc_argsize = sizeof(struct nlm_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_res_wrapper),
+ .pc_xdrressize = NLM3_nlm_res_sz,
+ .pc_name = "GRANTED",
+ },
+ [NLM_TEST_MSG] = {
+ .pc_func = nlmsvc_proc_test_msg,
+ .pc_decode = nlm_svc_decode_nlm_testargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "TEST_MSG",
+ },
+ [NLM_LOCK_MSG] = {
+ .pc_func = nlmsvc_proc_lock_msg,
+ .pc_decode = nlm_svc_decode_nlm_lockargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_lockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "LOCK_MSG",
+ },
+ [NLM_CANCEL_MSG] = {
+ .pc_func = nlmsvc_proc_cancel_msg,
+ .pc_decode = nlm_svc_decode_nlm_cancargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_cancargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "CANCEL_MSG",
+ },
+ [NLM_UNLOCK_MSG] = {
+ .pc_func = nlmsvc_proc_unlock_msg,
+ .pc_decode = nlm_svc_decode_nlm_unlockargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_unlockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNLOCK_MSG",
+ },
+ [NLM_GRANTED_MSG] = {
+ .pc_func = nlmsvc_proc_granted_msg,
+ .pc_decode = nlm_svc_decode_nlm_testargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_testargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "GRANTED_MSG",
+ },
+ [NLM_TEST_RES] = {
+ .pc_func = nlmsvc_proc_null,
+ .pc_decode = nlm_svc_decode_nlm_testres,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_testres),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "TEST_RES",
+ },
+ [NLM_LOCK_RES] = {
+ .pc_func = nlmsvc_proc_null,
+ .pc_decode = nlm_svc_decode_nlm_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_res),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "LOCK_RES",
+ },
+ [NLM_CANCEL_RES] = {
+ .pc_func = nlmsvc_proc_null,
+ .pc_decode = nlm_svc_decode_nlm_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_res),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "CANCEL_RES",
+ },
+ [NLM_UNLOCK_RES] = {
+ .pc_func = nlmsvc_proc_null,
+ .pc_decode = nlm_svc_decode_nlm_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_res),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNLOCK_RES",
+ },
+ [NLM_GRANTED_RES] = {
+ .pc_func = nlmsvc_proc_granted_res,
+ .pc_decode = nlm_svc_decode_nlm_res,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_res_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "GRANTED_RES",
+ },
+ [NLM_SM_NOTIFY] = {
+ .pc_func = nlmsvc_proc_sm_notify,
+ .pc_decode = nlm_svc_decode_nlm_notifyargs,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_notifyargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "SM_NOTIFY",
+ },
+ [17] = {
+ .pc_func = nlmsvc_proc_unused,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = 0,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNUSED",
+ },
+ [18] = {
+ .pc_func = nlmsvc_proc_unused,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = 0,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNUSED",
+ },
+ [19] = {
+ .pc_func = nlmsvc_proc_unused,
+ .pc_decode = xdrgen_svc_decode_void,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = 0,
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "UNUSED",
+ },
+ [NLM_SHARE] = {
+ .pc_func = nlmsvc_proc_share,
+ .pc_decode = nlm_svc_decode_nlm_shareargs,
+ .pc_encode = nlm_svc_encode_nlm_shareres,
+ .pc_argsize = sizeof(struct nlm_shareargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_shareres_wrapper),
+ .pc_xdrressize = NLM3_nlm_shareres_sz,
+ .pc_name = "SHARE",
+ },
+ [NLM_UNSHARE] = {
+ .pc_func = nlmsvc_proc_unshare,
+ .pc_decode = nlm_svc_decode_nlm_shareargs,
+ .pc_encode = nlm_svc_encode_nlm_shareres,
+ .pc_argsize = sizeof(struct nlm_shareargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_shareres_wrapper),
+ .pc_xdrressize = NLM3_nlm_shareres_sz,
+ .pc_name = "UNSHARE",
+ },
+ [NLM_NM_LOCK] = {
+ .pc_func = nlmsvc_proc_nm_lock,
+ .pc_decode = nlm_svc_decode_nlm_lockargs,
+ .pc_encode = nlm_svc_encode_nlm_res,
+ .pc_argsize = sizeof(struct nlm_lockargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nlm_res_wrapper),
+ .pc_xdrressize = NLM3_nlm_res_sz,
+ .pc_name = "NM_LOCK",
+ },
+ [NLM_FREE_ALL] = {
+ .pc_func = nlmsvc_proc_free_all,
+ .pc_decode = nlm_svc_decode_nlm_notify,
+ .pc_encode = xdrgen_svc_encode_void,
+ .pc_argsize = sizeof(struct nlm_notify_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = 0,
+ .pc_xdrressize = XDR_void,
+ .pc_name = "FREE_ALL",
+ },
+};
+
+/*
+ * Storage requirements for XDR arguments and results
+ */
+union nlmsvc_xdrstore {
+ struct nlm_testargs_wrapper testargs;
+ struct nlm_lockargs_wrapper lockargs;
+ struct nlm_cancargs_wrapper cancargs;
+ struct nlm_unlockargs_wrapper unlockargs;
+ struct nlm_notifyargs_wrapper notifyargs;
+ struct nlm_shareargs_wrapper shareargs;
+ struct nlm_notify_wrapper notify;
+ struct nlm_testres_wrapper testres;
+ struct nlm_res_wrapper res;
+ struct nlm_shareres_wrapper shareres;
+};
+
+/*
+ * NLMv1 defines only procedures 1 - 15. Linux lockd also implements
+ * procedures 0 (NULL) and 16 (SM_NOTIFY).
+ */
+const struct svc_version nlmsvc_version1 = {
+ .vs_vers = 1,
+ .vs_nproc = 17,
+ .vs_proc = nlmsvc_procedures,
+ .vs_dispatch = nlmsvc_dispatch,
+ .vs_xdrsize = sizeof(union nlmsvc_xdrstore),
+};
+
+const struct svc_version nlmsvc_version3 = {
+ .vs_vers = 3,
+ .vs_nproc = ARRAY_SIZE(nlmsvc_procedures),
+ .vs_proc = nlmsvc_procedures,
+ .vs_dispatch = nlmsvc_dispatch,
+ .vs_xdrsize = sizeof(union nlmsvc_xdrstore),
+};
diff --git a/fs/lockd/svcshare.c b/fs/lockd/svcshare.c
new file mode 100644
index 000000000..a58b7035b
--- /dev/null
+++ b/fs/lockd/svcshare.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/lockd/svcshare.c
+ *
+ * Management of DOS shares.
+ *
+ * Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/time.h>
+#include <linux/unistd.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc.h>
+
+#include "lockd.h"
+#include "share.h"
+
+static inline int
+nlm_cmp_owner(struct lockd_share *share, struct xdr_netobj *oh)
+{
+ return share->s_owner.len == oh->len
+ && !memcmp(share->s_owner.data, oh->data, oh->len);
+}
+
+/*
+ * Recompute s_access / s_mode as the union of every (access, deny) pair
+ * whose bit is currently set in s_access_deny_bmap.
+ */
+static void nlm_recompute_share(struct lockd_share *share)
+{
+ u32 new_access = 0, new_mode = 0;
+ unsigned int i;
+
+ for (i = 0; i < 16; i++) {
+ if (share->s_access_deny_bmap & BIT(i)) {
+ new_access |= i >> 2;
+ new_mode |= i & 3;
+ }
+ }
+ share->s_access = new_access;
+ share->s_mode = new_mode;
+}
+
+/**
+ * nlmsvc_share_file - create a share
+ * @host: Network client peer
+ * @file: File to be shared
+ * @oh: Share owner handle
+ * @access: Requested access mode
+ * @mode: Requested file sharing mode
+ *
+ * Returns an NLM status code.
+ */
+__be32
+nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file,
+ struct xdr_netobj *oh, u32 access, u32 mode)
+{
+ struct lockd_share *share;
+ u8 *ohdata;
+
+ if (nlmsvc_file_cannot_lock(file))
+ return nlm_lck_denied_nolocks;
+
+ for (share = file->f_shares; share; share = share->s_next) {
+ if (share->s_host == host && nlm_cmp_owner(share, oh))
+ goto update;
+ if ((access & share->s_mode) || (mode & share->s_access))
+ return nlm_lck_denied;
+ }
+
+ share = kmalloc(sizeof(*share) + oh->len, GFP_KERNEL);
+ if (share == NULL)
+ return nlm_lck_denied_nolocks;
+
+ /* Copy owner handle */
+ ohdata = (u8 *) (share + 1);
+ memcpy(ohdata, oh->data, oh->len);
+
+ share->s_file = file;
+ share->s_host = host;
+ share->s_owner.data = ohdata;
+ share->s_owner.len = oh->len;
+ share->s_access_deny_bmap = 0;
+ share->s_next = file->f_shares;
+ file->f_shares = share;
+
+update:
+ share->s_access_deny_bmap |= LOCKD_FSH_BIT(access, mode);
+ nlm_recompute_share(share);
+ return nlm_granted;
+}
+
+/**
+ * nlmsvc_unshare_file - delete a share
+ * @host: Network client peer
+ * @file: File to be unshared
+ * @oh: Share owner handle
+ * @access: Access mode of the SHARE being released
+ * @mode: Deny mode of the SHARE being released
+ *
+ * Returns an NLM status code.
+ */
+__be32
+nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
+ struct xdr_netobj *oh, u32 access, u32 mode)
+{
+ struct lockd_share *share, **shpp;
+
+ if (nlmsvc_file_cannot_lock(file))
+ return nlm_lck_denied_nolocks;
+
+ for (shpp = &file->f_shares; (share = *shpp) != NULL;
+ shpp = &share->s_next) {
+ if (share->s_host == host && nlm_cmp_owner(share, oh)) {
+ share->s_access_deny_bmap &= ~LOCKD_FSH_BIT(access, mode);
+ nlm_recompute_share(share);
+ if (!share->s_access_deny_bmap) {
+ *shpp = share->s_next;
+ kfree(share);
+ }
+ return nlm_granted;
+ }
+ }
+
+ /* X/Open spec says return success even if there was no
+ * corresponding share. */
+ return nlm_granted;
+}
+
+/*
+ * Traverse all shares for a given file, and delete
+ * those owned by the given (type of) host
+ */
+void nlmsvc_traverse_shares(struct nlm_host *host, struct nlm_file *file,
+ nlm_host_match_fn_t match)
+{
+ struct lockd_share *share, **shpp;
+
+ shpp = &file->f_shares;
+ while ((share = *shpp) != NULL) {
+ if (match(share->s_host, host)) {
+ *shpp = share->s_next;
+ kfree(share);
+ continue;
+ }
+ shpp = &share->s_next;
+ }
+}
diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c
new file mode 100644
index 000000000..ffab76278
--- /dev/null
+++ b/fs/lockd/svcsubs.c
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/fs/lockd/svcsubs.c
+ *
+ * Various support routines for the NLM server.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/time.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/sunrpc/addr.h>
+#include <linux/module.h>
+#include <linux/mount.h>
+
+#include "lockd.h"
+#include "share.h"
+
+#define NLMDBG_FACILITY NLMDBG_SVCSUBS
+
+
+/*
+ * Global file hash table
+ */
+#define FILE_HASH_BITS 7
+#define FILE_NRHASH (1<<FILE_HASH_BITS)
+static struct hlist_head nlm_files[FILE_NRHASH];
+static DEFINE_MUTEX(nlm_file_mutex);
+
+#ifdef CONFIG_SUNRPC_DEBUG
+static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f)
+{
+ u32 *fhp = (u32*)f->data;
+
+ /* print the first 32 bytes of the fh */
+ dprintk("lockd: %s (%08x %08x %08x %08x %08x %08x %08x %08x)\n",
+ msg, fhp[0], fhp[1], fhp[2], fhp[3],
+ fhp[4], fhp[5], fhp[6], fhp[7]);
+}
+
+static inline void nlm_debug_print_file(char *msg, struct nlm_file *file)
+{
+ struct inode *inode = nlmsvc_file_inode(file);
+
+ dprintk("lockd: %s %s/%llu\n",
+ msg, inode->i_sb->s_id, inode->i_ino);
+}
+#else
+static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f)
+{
+ return;
+}
+
+static inline void nlm_debug_print_file(char *msg, struct nlm_file *file)
+{
+ return;
+}
+#endif
+
+static inline unsigned int file_hash(struct nfs_fh *f)
+{
+ unsigned int tmp=0;
+ int i;
+ for (i = 0; i < LOCKD_FH_HASH_SIZE; i++)
+ tmp += f->data[i];
+ return tmp & (FILE_NRHASH - 1);
+}
+
+int lock_to_openmode(struct file_lock *lock)
+{
+ return lock_is_write(lock) ? O_WRONLY : O_RDONLY;
+}
+
+/*
+ * Open the file. Note that if we're reexporting, for example,
+ * this could block the lockd thread for a while.
+ *
+ * We have to make sure we have the right credential to open
+ * the file.
+ *
+ * @mode is O_RDONLY, O_WRONLY, or O_RDWR. O_RDWR means success
+ * is achieved with EITHER O_RDONLY or O_WRONLY; it does not
+ * require both.
+ */
+static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
+ struct nlm_file *file, int mode)
+{
+ const struct nlmsvc_binding *ops;
+ __be32 nlmerr = nlm__int__failed;
+ __be32 deferred = 0;
+ int error;
+ int m;
+
+ rcu_read_lock();
+ ops = rcu_dereference(nlmsvc_ops);
+ if (!ops || !try_module_get(ops->owner)) {
+ rcu_read_unlock();
+ return nlm__int__failed;
+ }
+ rcu_read_unlock();
+
+ for (m = O_RDONLY; m <= O_WRONLY; m++) {
+ struct file **fp = &file->f_file[m];
+
+ if (mode != O_RDWR && mode != m)
+ continue;
+ if (*fp) {
+ module_put(ops->owner);
+ return nlm_granted;
+ }
+
+ error = ops->fopen(rqstp, &file->f_handle, fp, m);
+ if (!error) {
+ module_put(ops->owner);
+ return nlm_granted;
+ }
+
+ dprintk("lockd: open failed (errno %d)\n", error);
+ switch (error) {
+ case -EWOULDBLOCK:
+ nlmerr = nlm__int__drop_reply;
+ deferred = nlmerr;
+ break;
+ case -ESTALE:
+ nlmerr = nlm__int__stale_fh;
+ break;
+ default:
+ nlmerr = nlm__int__failed;
+ break;
+ }
+ }
+
+ module_put(ops->owner);
+ return deferred ? deferred : nlmerr;
+}
+
+/*
+ * Lookup file info. If it doesn't exist, create a file info struct
+ * and open a (VFS) file for the given inode.
+ */
+__be32
+nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result,
+ struct lockd_lock *lock, int mode)
+{
+ struct nlm_file *file;
+ unsigned int hash;
+ __be32 nfserr;
+
+ nlm_debug_print_fh("nlm_lookup_file", &lock->fh);
+
+ hash = file_hash(&lock->fh);
+
+ /* Lock file table */
+ mutex_lock(&nlm_file_mutex);
+
+ hlist_for_each_entry(file, &nlm_files[hash], f_list)
+ if (!nfs_compare_fh(&file->f_handle, &lock->fh)) {
+ mutex_lock(&file->f_mutex);
+ nfserr = nlm_do_fopen(rqstp, file, mode);
+ mutex_unlock(&file->f_mutex);
+ if (nfserr)
+ goto out_unlock;
+ goto found;
+ }
+ nlm_debug_print_fh("creating file for", &lock->fh);
+
+ nfserr = nlm_lck_denied_nolocks;
+ file = kzalloc_obj(*file);
+ if (!file)
+ goto out_free;
+
+ memcpy(&file->f_handle, &lock->fh, sizeof(struct nfs_fh));
+ mutex_init(&file->f_mutex);
+ INIT_HLIST_NODE(&file->f_list);
+ INIT_LIST_HEAD(&file->f_blocks);
+
+ nfserr = nlm_do_fopen(rqstp, file, mode);
+ if (nfserr)
+ goto out_free;
+
+ hlist_add_head(&file->f_list, &nlm_files[hash]);
+
+found:
+ dprintk("lockd: found file %p (count %d)\n", file, file->f_count);
+ *result = file;
+ file->f_count++;
+
+out_unlock:
+ mutex_unlock(&nlm_file_mutex);
+ return nfserr;
+
+out_free:
+ kfree(file);
+ goto out_unlock;
+}
+
+/*
+ * Release the struct file references held by a nlm_file.
+ */
+static void nlm_release_files(struct nlm_file *file)
+{
+ const struct nlmsvc_binding *ops;
+ bool have_ops;
+
+ rcu_read_lock();
+ ops = rcu_dereference(nlmsvc_ops);
+ have_ops = ops && try_module_get(ops->owner);
+ rcu_read_unlock();
+
+ if (have_ops) {
+ if (file->f_file[O_RDONLY])
+ ops->fclose(file->f_file[O_RDONLY]);
+ if (file->f_file[O_WRONLY])
+ ops->fclose(file->f_file[O_WRONLY]);
+ module_put(ops->owner);
+ } else {
+ if (file->f_file[O_RDONLY])
+ fput(file->f_file[O_RDONLY]);
+ if (file->f_file[O_WRONLY])
+ fput(file->f_file[O_WRONLY]);
+ }
+}
+
+/*
+ * Delete a file after having released all locks, blocks and shares
+ */
+static inline void
+nlm_delete_file(struct nlm_file *file)
+{
+ nlm_debug_print_file("closing file", file);
+ if (!hlist_unhashed(&file->f_list)) {
+ hlist_del(&file->f_list);
+ nlm_release_files(file);
+ kfree(file);
+ } else {
+ printk(KERN_WARNING "lockd: attempt to release unknown file!\n");
+ }
+}
+
+static int nlm_unlock_files(struct nlm_file *file, const struct file_lock *fl)
+{
+ struct file_lock lock;
+
+ locks_init_lock(&lock);
+ lock.c.flc_type = F_UNLCK;
+ lock.fl_start = 0;
+ lock.fl_end = OFFSET_MAX;
+ lock.c.flc_owner = fl->c.flc_owner;
+ lock.c.flc_pid = fl->c.flc_pid;
+ lock.c.flc_flags = FL_POSIX;
+
+ lock.c.flc_file = file->f_file[O_RDONLY];
+ if (lock.c.flc_file && vfs_lock_file(lock.c.flc_file, F_SETLK, &lock, NULL))
+ goto out_err;
+ lock.c.flc_file = file->f_file[O_WRONLY];
+ if (lock.c.flc_file && vfs_lock_file(lock.c.flc_file, F_SETLK, &lock, NULL))
+ goto out_err;
+ return 0;
+out_err:
+ pr_warn("lockd: unlock failure in %s:%d\n", __FILE__, __LINE__);
+ return 1;
+}
+
+/*
+ * Loop over all locks on the given file and perform the specified
+ * action.
+ */
+static int
+nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file,
+ nlm_host_match_fn_t match)
+{
+ struct inode *inode = nlmsvc_file_inode(file);
+ struct file_lock *fl;
+ struct file_lock_context *flctx = locks_inode_context(inode);
+ struct nlm_host *lockhost;
+
+ if (!flctx || list_empty_careful(&flctx->flc_posix))
+ return 0;
+again:
+ file->f_locks = 0;
+ spin_lock(&flctx->flc_lock);
+ for_each_file_lock(fl, &flctx->flc_posix) {
+ if (fl->fl_lmops != &nlmsvc_lock_operations)
+ continue;
+
+ /* update current lock count */
+ file->f_locks++;
+
+ lockhost = ((struct nlm_lockowner *) fl->c.flc_owner)->host;
+ if (match(lockhost, host)) {
+
+ spin_unlock(&flctx->flc_lock);
+ if (nlm_unlock_files(file, fl))
+ return 1;
+ goto again;
+ }
+ }
+ spin_unlock(&flctx->flc_lock);
+
+ return 0;
+}
+
+static int
+nlmsvc_always_match(void *dummy1, struct nlm_host *dummy2)
+{
+ return 1;
+}
+
+/*
+ * Inspect a single file
+ */
+static inline int
+nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, nlm_host_match_fn_t match)
+{
+ nlmsvc_traverse_blocks(host, file, match);
+ nlmsvc_traverse_shares(host, file, match);
+ return nlm_traverse_locks(host, file, match);
+}
+
+/*
+ * Quick check whether there are still any locks, blocks or
+ * shares on a given file.
+ */
+static inline int
+nlm_file_inuse(struct nlm_file *file)
+{
+ struct inode *inode = nlmsvc_file_inode(file);
+ struct file_lock *fl;
+ struct file_lock_context *flctx = locks_inode_context(inode);
+
+ if (file->f_count || !list_empty(&file->f_blocks) || file->f_shares)
+ return 1;
+
+ if (flctx && !list_empty_careful(&flctx->flc_posix)) {
+ spin_lock(&flctx->flc_lock);
+ for_each_file_lock(fl, &flctx->flc_posix) {
+ if (fl->fl_lmops == &nlmsvc_lock_operations) {
+ spin_unlock(&flctx->flc_lock);
+ return 1;
+ }
+ }
+ spin_unlock(&flctx->flc_lock);
+ }
+ file->f_locks = 0;
+ return 0;
+}
+
+static void nlm_file_release(struct nlm_file *file)
+{
+ if (!nlm_file_inuse(file))
+ nlm_delete_file(file);
+}
+
+/*
+ * Loop over all files in the file table.
+ */
+static int
+nlm_traverse_files(void *data, nlm_host_match_fn_t match,
+ int (*is_failover_file)(void *data, struct nlm_file *file))
+{
+ struct nlm_file *file, *next;
+ int i, ret = 0;
+
+ mutex_lock(&nlm_file_mutex);
+ for (i = 0; i < FILE_NRHASH; i++) {
+ file = hlist_entry_safe(nlm_files[i].first,
+ struct nlm_file, f_list);
+ if (file)
+ file->f_count++;
+ while (file) {
+ /*
+ * Pin the next neighbour before we drop the mutex
+ * for nlm_inspect_file(); a concurrent
+ * nlm_release_file() under the same mutex would
+ * otherwise be free to unlink and kfree it during
+ * the unlock window, leaving us to dereference a
+ * freed slab when we walked to next afterwards.
+ */
+ next = hlist_entry_safe(file->f_list.next,
+ struct nlm_file, f_list);
+ if (next)
+ next->f_count++;
+
+ if (!is_failover_file || is_failover_file(data, file)) {
+ mutex_unlock(&nlm_file_mutex);
+
+ if (nlm_inspect_file(data, file, match))
+ ret = 1;
+
+ mutex_lock(&nlm_file_mutex);
+ }
+
+ file->f_count--;
+ nlm_file_release(file);
+ file = next;
+ }
+ }
+ mutex_unlock(&nlm_file_mutex);
+ return ret;
+}
+
+/*
+ * Release file. If there are no more remote locks on this file,
+ * close it and free the handle.
+ *
+ * Note that we can't do proper reference counting without major
+ * contortions because the code in fs/locks.c creates, deletes and
+ * splits locks without notification. Our only way is to walk the
+ * entire lock list each time we remove a lock.
+ */
+void
+nlm_release_file(struct nlm_file *file)
+{
+ dprintk("lockd: nlm_release_file(%p, ct = %d)\n",
+ file, file->f_count);
+
+ /* Lock file table */
+ mutex_lock(&nlm_file_mutex);
+
+ /* If there are no more locks etc, delete the file */
+ if (--file->f_count == 0 && !nlm_file_inuse(file))
+ nlm_delete_file(file);
+
+ mutex_unlock(&nlm_file_mutex);
+}
+
+/*
+ * Helpers function for resource traversal
+ *
+ * nlmsvc_mark_host:
+ * used by the garbage collector; simply sets h_inuse only for those
+ * hosts, which passed network check.
+ * Always returns 0.
+ *
+ * nlmsvc_same_host:
+ * returns 1 iff the two hosts match. Used to release
+ * all resources bound to a specific host.
+ *
+ * nlmsvc_is_client:
+ * returns 1 iff the host is a client.
+ * Used by nlmsvc_invalidate_all
+ */
+
+static int
+nlmsvc_mark_host(void *data, struct nlm_host *hint)
+{
+ struct nlm_host *host = data;
+
+ if ((hint->net == NULL) ||
+ (host->net == hint->net))
+ host->h_inuse = 1;
+ return 0;
+}
+
+static int
+nlmsvc_same_host(void *data, struct nlm_host *other)
+{
+ struct nlm_host *host = data;
+
+ return host == other;
+}
+
+static int
+nlmsvc_is_client(void *data, struct nlm_host *dummy)
+{
+ struct nlm_host *host = data;
+
+ if (host->h_server) {
+ /* we are destroying locks even though the client
+ * hasn't asked us too, so don't unmonitor the
+ * client
+ */
+ if (host->h_nsmhandle)
+ host->h_nsmhandle->sm_sticky = 1;
+ return 1;
+ } else
+ return 0;
+}
+
+/*
+ * Mark all hosts that still hold resources
+ */
+void
+nlmsvc_mark_resources(struct net *net)
+{
+ struct nlm_host hint;
+
+ dprintk("lockd: %s for net %x\n", __func__, net ? net->ns.inum : 0);
+ hint.net = net;
+ nlm_traverse_files(&hint, nlmsvc_mark_host, NULL);
+}
+
+/*
+ * Release all resources held by the given client
+ */
+void
+nlmsvc_free_host_resources(struct nlm_host *host)
+{
+ dprintk("lockd: nlmsvc_free_host_resources\n");
+
+ if (nlm_traverse_files(host, nlmsvc_same_host, NULL)) {
+ printk(KERN_WARNING
+ "lockd: couldn't remove all locks held by %s\n",
+ host->h_name);
+ BUG();
+ }
+}
+
+/**
+ * nlmsvc_invalidate_all - remove all locks held for clients
+ *
+ * Release all locks held by NFS clients.
+ *
+ */
+void
+nlmsvc_invalidate_all(void)
+{
+ /*
+ * Previously, the code would call
+ * nlmsvc_free_host_resources for each client in
+ * turn, which is about as inefficient as it gets.
+ * Now we just do it once in nlm_traverse_files.
+ */
+ nlm_traverse_files(NULL, nlmsvc_is_client, NULL);
+}
+
+
+static int
+nlmsvc_match_sb(void *datap, struct nlm_file *file)
+{
+ struct super_block *sb = datap;
+
+ return sb == nlmsvc_file_inode(file)->i_sb;
+}
+
+/**
+ * nlmsvc_unlock_all_by_sb - release locks held on this file system
+ * @sb: super block
+ *
+ * Release all locks held by clients accessing this file system.
+ */
+int
+nlmsvc_unlock_all_by_sb(struct super_block *sb)
+{
+ int ret;
+
+ ret = nlm_traverse_files(sb, nlmsvc_always_match, nlmsvc_match_sb);
+ return ret ? -EIO : 0;
+}
+EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_sb);
+
+static int
+nlmsvc_match_ip(void *datap, struct nlm_host *host)
+{
+ return rpc_cmp_addr(nlm_srcaddr(datap), (struct sockaddr *)host);
+}
+
+/**
+ * nlmsvc_unlock_all_by_ip - release local locks by IP address
+ * @server_addr: server's IP address as seen by clients
+ *
+ * Release all locks held by clients accessing this host
+ * via the passed in IP address.
+ */
+int
+nlmsvc_unlock_all_by_ip(struct sockaddr *server_addr)
+{
+ int ret;
+
+ ret = nlm_traverse_files(server_addr, nlmsvc_match_ip, NULL);
+ return ret ? -EIO : 0;
+}
+EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_ip);
diff --git a/fs/lockd/trace.c b/fs/lockd/trace.c
new file mode 100644
index 000000000..d9a6ff6e6
--- /dev/null
+++ b/fs/lockd/trace.c
@@ -0,0 +1,3 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include "trace.h"
diff --git a/fs/lockd/trace.h b/fs/lockd/trace.h
new file mode 100644
index 000000000..a11d04e8c
--- /dev/null
+++ b/fs/lockd/trace.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM lockd
+
+#if !defined(_TRACE_LOCKD_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_LOCKD_H
+
+#include <linux/tracepoint.h>
+#include <linux/crc32.h>
+#include <linux/nfs.h>
+
+#include "lockd.h"
+
+#ifdef CONFIG_LOCKD_V4
+#define NLM_STATUS_LIST \
+ nlm_status_code(LCK_GRANTED) \
+ nlm_status_code(LCK_DENIED) \
+ nlm_status_code(LCK_DENIED_NOLOCKS) \
+ nlm_status_code(LCK_BLOCKED) \
+ nlm_status_code(LCK_DENIED_GRACE_PERIOD) \
+ nlm_status_code(DEADLCK) \
+ nlm_status_code(ROFS) \
+ nlm_status_code(STALE_FH) \
+ nlm_status_code(FBIG) \
+ nlm_status_code_end(FAILED)
+#else
+#define NLM_STATUS_LIST \
+ nlm_status_code(LCK_GRANTED) \
+ nlm_status_code(LCK_DENIED) \
+ nlm_status_code(LCK_DENIED_NOLOCKS) \
+ nlm_status_code(LCK_BLOCKED) \
+ nlm_status_code_end(LCK_DENIED_GRACE_PERIOD)
+#endif
+
+#undef nlm_status_code
+#undef nlm_status_code_end
+#define nlm_status_code(x) TRACE_DEFINE_ENUM(NLM_##x);
+#define nlm_status_code_end(x) TRACE_DEFINE_ENUM(NLM_##x);
+
+NLM_STATUS_LIST
+
+#undef nlm_status_code
+#undef nlm_status_code_end
+#define nlm_status_code(x) { NLM_##x, #x },
+#define nlm_status_code_end(x) { NLM_##x, #x }
+
+#define show_nlm_status(x) __print_symbolic(x, NLM_STATUS_LIST)
+
+DECLARE_EVENT_CLASS(nlmclnt_lock_event,
+ TP_PROTO(
+ const struct lockd_lock *lock,
+ const struct sockaddr *addr,
+ unsigned int addrlen,
+ __be32 status
+ ),
+
+ TP_ARGS(lock, addr, addrlen, status),
+
+ TP_STRUCT__entry(
+ __field(u32, oh)
+ __field(u32, svid)
+ __field(u32, fh)
+ __field(unsigned long, status)
+ __field(loff_t, start)
+ __field(loff_t, end)
+ __sockaddr(addr, addrlen)
+ ),
+
+ TP_fast_assign(
+ __entry->oh = ~crc32_le(0xffffffff, lock->oh.data, lock->oh.len);
+ __entry->svid = lock->svid;
+ __entry->fh = nfs_fhandle_hash(&lock->fh);
+ __entry->start = lock->fl.fl_start;
+ __entry->end = lock->fl.fl_end;
+ __entry->status = be32_to_cpu(status);
+ __assign_sockaddr(addr, addr, addrlen);
+ ),
+
+ TP_printk(
+ "addr=%pISpc oh=0x%08x svid=0x%08x fh=0x%08x start=%lld end=%lld status=%s",
+ __get_sockaddr(addr), __entry->oh, __entry->svid,
+ __entry->fh, __entry->start, __entry->end,
+ show_nlm_status(__entry->status)
+ )
+);
+
+#define DEFINE_NLMCLNT_EVENT(name) \
+ DEFINE_EVENT(nlmclnt_lock_event, name, \
+ TP_PROTO( \
+ const struct lockd_lock *lock, \
+ const struct sockaddr *addr, \
+ unsigned int addrlen, \
+ __be32 status \
+ ), \
+ TP_ARGS(lock, addr, addrlen, status))
+
+DEFINE_NLMCLNT_EVENT(nlmclnt_test);
+DEFINE_NLMCLNT_EVENT(nlmclnt_lock);
+DEFINE_NLMCLNT_EVENT(nlmclnt_unlock);
+DEFINE_NLMCLNT_EVENT(nlmclnt_grant);
+
+#endif /* _TRACE_LOCKD_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE trace
+#include <trace/define_trace.h>
diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h
new file mode 100644
index 000000000..a1126cca9
--- /dev/null
+++ b/fs/lockd/xdr.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * XDR types for the NLM protocol
+ *
+ * Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#ifndef _LOCKD_XDR_H
+#define _LOCKD_XDR_H
+
+#include <linux/fs.h>
+#include <linux/filelock.h>
+#include <linux/nfs.h>
+#include <linux/sunrpc/xdr.h>
+
+#define SM_MAXSTRLEN 1024
+#define SM_PRIV_SIZE 16
+
+struct nsm_private {
+ unsigned char data[SM_PRIV_SIZE];
+};
+
+#define NLM_MAXCOOKIELEN 32
+#define NLM_MAXSTRLEN 1024
+
+#define nlm_granted cpu_to_be32(NLM_LCK_GRANTED)
+#define nlm_lck_denied cpu_to_be32(NLM_LCK_DENIED)
+#define nlm_lck_denied_nolocks cpu_to_be32(NLM_LCK_DENIED_NOLOCKS)
+#define nlm_lck_blocked cpu_to_be32(NLM_LCK_BLOCKED)
+#define nlm_lck_denied_grace_period cpu_to_be32(NLM_LCK_DENIED_GRACE_PERIOD)
+
+/* Lock info passed via NLM */
+struct lockd_lock {
+ char * caller;
+ unsigned int len; /* length of "caller" */
+ struct nfs_fh fh;
+ struct xdr_netobj oh;
+ u32 svid;
+ u64 lock_start;
+ u64 lock_len;
+ struct file_lock fl;
+};
+
+/*
+ * NLM cookies. Technically they can be 1K, but Linux only uses 8 bytes.
+ * FreeBSD uses 16, Apple Mac OS X 10.3 uses 20. Therefore we set it to
+ * 32 bytes.
+ */
+
+struct lockd_cookie {
+ unsigned char data[NLM_MAXCOOKIELEN];
+ unsigned int len;
+};
+
+/*
+ * Generic lockd arguments for all but sm_notify
+ */
+struct lockd_args {
+ struct lockd_cookie cookie;
+ struct lockd_lock lock;
+ u32 block;
+ u32 reclaim;
+ u32 state;
+};
+
+/*
+ * Generic lockd result
+ */
+struct lockd_res {
+ struct lockd_cookie cookie;
+ __be32 status;
+ struct lockd_lock lock;
+};
+
+/*
+ * statd callback when client has rebooted
+ */
+struct lockd_reboot {
+ char *mon;
+ unsigned int len;
+ u32 state;
+ struct nsm_private priv;
+};
+
+#endif /* _LOCKD_XDR_H */