]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
lockd: Use xdrgen XDR functions for the NLMv3 TEST procedure
authorChuck Lever <chuck.lever@oracle.com>
Tue, 12 May 2026 18:13:49 +0000 (14:13 -0400)
committerChuck Lever <cel@kernel.org>
Tue, 9 Jun 2026 20:32:59 +0000 (16:32 -0400)
The NLM TEST procedure requires host and file lookups to check
lock state, operations that will be common across multiple NLM
procedures being migrated to xdrgen. Introducing the
nlm3svc_lookup_host() and nlm3svc_lookup_file() helpers now keeps
these common patterns in one place for subsequent conversions in
this series.

This patch converts the TEST procedure to use xdrgen functions
nlm_svc_decode_nlm_testargs and nlm_svc_encode_nlm_testres
generated from the NLM version 3 protocol specification. The
procedure handler is rewritten to use xdrgen types through wrapper
structures that bridge between generated code and the legacy
lockd_lock representation still used by the core lockd logic.

Setting pc_argzero to zero is safe because the generated decoder
fills the argp->xdrgen subfields before the procedure runs, so the
zeroing memset performed by the dispatch layer is not needed. The
lock member of the wrapper is populated explicitly in
nlm3svc_lookup_file() rather than relying on zero-initialization.

The conflicting holder's offset and length are saturated to
NLM_OFFSET_MAX when constructing the reply. A conflicting lock
established by an NLMv4 client or by a local process can sit
beyond the NLMv3 signed 32-bit range, and copying fl_start and
fl_end straight into the unsigned 32-bit XDR fields would wrap
and report a bogus range. The previous hand-written encoder in
svcxdr_encode_holder() used loff_t_to_s32() for the same reason,
but this patch series intends to separate the concerns of data
conversion (XDR) from dealing with local byte range constraints,
so clamping is hoisted into the proc function.

The previous hand-written decoder in svcxdr_decode_cookie()
rewrote a zero-length NLM cookie into a four-byte zero cookie,
with a comment attributing the substitution to HP-UX clients.
The xdrgen-generated netobj decoder performs no such rewrite, so
a zero-length request cookie now round-trips unchanged into the
reply. HP-UX has reached end of support, and NLM_TEST reply
matching relies on the RPC XID rather than the NLM cookie, so
the workaround is dropped intentionally here.

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/lockd/lockd.h
fs/lockd/svcproc.c

index 5c79681b7e9591f65968ffb28c081ebbf845de49..0be0dac59ea29b1fc67e1a4883c64d9d9d83a58a 100644 (file)
@@ -422,6 +422,29 @@ static inline int nlm_compare_locks(const struct file_lock *fl1,
             &&(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
index ad37f3611eeae11aba9f4673324411fb96c12b8b..7794e6f88a711f144887e5b512e94ad9c68c1768 100644 (file)
 
 #define NLMDBG_FACILITY                NLMDBG_CLIENT
 
+/*
+ * 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);
+
+static struct nlm_host *
+nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
+{
+       struct nlm_host *host;
+
+       if (!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;
+}
+
 #ifdef CONFIG_LOCKD_V4
 static inline __be32 cast_status(__be32 status)
 {
@@ -178,10 +278,79 @@ __nlmsvc_proc_test(struct svc_rqst *rqstp, struct lockd_res *resp)
        return rc;
 }
 
-static __be32
-nlmsvc_proc_test(struct svc_rqst *rqstp)
+/**
+ * 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)
 {
-       return __nlmsvc_proc_test(rqstp, rqstp->rq_resp);
+       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
@@ -592,15 +761,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = {
                .pc_xdrressize  = XDR_void,
                .pc_name        = "NULL",
        },
-       [NLMPROC_TEST] = {
-               .pc_func = nlmsvc_proc_test,
-               .pc_decode = nlmsvc_decode_testargs,
-               .pc_encode = nlmsvc_encode_testres,
-               .pc_argsize = sizeof(struct lockd_args),
-               .pc_argzero = sizeof(struct lockd_args),
-               .pc_ressize = sizeof(struct lockd_res),
-               .pc_xdrressize = Ck+St+2+No+Rg,
-               .pc_name = "TEST",
+       [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",
        },
        [NLMPROC_LOCK] = {
                .pc_func = nlmsvc_proc_lock,
@@ -828,6 +997,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = {
  * Storage requirements for XDR arguments and results
  */
 union nlmsvc_xdrstore {
+       struct nlm_testargs_wrapper     testargs;
+       struct nlm_testres_wrapper      testres;
        struct lockd_args               args;
        struct lockd_res                res;
        struct lockd_reboot             reboot;