From: Chuck Lever Date: Tue, 12 May 2026 18:13:49 +0000 (-0400) Subject: lockd: Use xdrgen XDR functions for the NLMv3 TEST procedure X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3031fd999e2d144f865b660cec819179c9a20e06;p=thirdparty%2Fkernel%2Flinux.git lockd: Use xdrgen XDR functions for the NLMv3 TEST procedure 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 Signed-off-by: Chuck Lever --- diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 5c79681b7e959..0be0dac59ea29 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -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 diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index ad37f3611eeae..7794e6f88a711 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -26,6 +26,106 @@ #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;