mirror of
				https://git.proxmox.com/git/mirror_zfs
				synced 2025-10-31 11:44:51 +00:00 
			
		
		
		
	 a9a4290173
			
		
	
	
		a9a4290173
		
			
		
	
	
	
	
		
			
			#16047 notes that include/os/freebsd/spl/rpc/xdr.h carried an (apparently) incompatible license. While looking into it, it seems that this file is actually unnecessary these days - FreeBSD's kernel XDR has XDR_CONTROL, xdrmem_control and XDR_GET_BYTES_AVAIL, while userspace has XDR_CONTROL and xdrmem_control, and our implementation of XDR_GET_BYTES_AVAIL for libspl works nicely with it. So this removes that file outright. To keep the includes in nvpair.c tidy, I've made a few small adjustments to the Linux headers. By definition, rpc/types.h provides bool_t and is included before rpc/xdr.h, so I've created rpc/types.h for Linux. This isn't necessary for userspace; both FreeBSD native and tirpc on Linux already have these headers set up correctly. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Rob Norris <robn@despairlabs.com> Sponsored-by: https://despairlabs.com/sponsor/ Closes #16047 Closes #16051
		
			
				
	
	
		
			514 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			514 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *  Copyright (c) 2008-2010 Sun Microsystems, Inc.
 | |
|  *  Written by Ricardo Correia <Ricardo.M.Correia@Sun.COM>
 | |
|  *
 | |
|  *  This file is part of the SPL, Solaris Porting Layer.
 | |
|  *
 | |
|  *  The SPL is free software; you can redistribute it and/or modify it
 | |
|  *  under the terms of the GNU General Public License as published by the
 | |
|  *  Free Software Foundation; either version 2 of the License, or (at your
 | |
|  *  option) any later version.
 | |
|  *
 | |
|  *  The SPL is distributed in the hope that it will be useful, but WITHOUT
 | |
|  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
|  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 | |
|  *  for more details.
 | |
|  *
 | |
|  *  You should have received a copy of the GNU General Public License along
 | |
|  *  with the SPL.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  *  Solaris Porting Layer (SPL) XDR Implementation.
 | |
|  */
 | |
| 
 | |
| #include <linux/string.h>
 | |
| #include <sys/kmem.h>
 | |
| #include <sys/debug.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/sysmacros.h>
 | |
| #include <rpc/types.h>
 | |
| #include <rpc/xdr.h>
 | |
| 
 | |
| /*
 | |
|  * SPL's XDR mem implementation.
 | |
|  *
 | |
|  * This is used by libnvpair to serialize/deserialize the name-value pair data
 | |
|  * structures into byte arrays in a well-defined and portable manner.
 | |
|  *
 | |
|  * These data structures are used by the DMU/ZFS to flexibly manipulate various
 | |
|  * information in memory and later serialize it/deserialize it to disk.
 | |
|  * Examples of usages include the pool configuration, lists of pool and dataset
 | |
|  * properties, etc.
 | |
|  *
 | |
|  * Reference documentation for the XDR representation and XDR operations can be
 | |
|  * found in RFC 1832 and xdr(3), respectively.
 | |
|  *
 | |
|  * ===  Implementation shortcomings ===
 | |
|  *
 | |
|  * It is assumed that the following C types have the following sizes:
 | |
|  *
 | |
|  * char/unsigned char:      1 byte
 | |
|  * short/unsigned short:    2 bytes
 | |
|  * int/unsigned int:        4 bytes
 | |
|  * longlong_t/u_longlong_t: 8 bytes
 | |
|  *
 | |
|  * The C standard allows these types to be larger (and in the case of ints,
 | |
|  * shorter), so if that is the case on some compiler/architecture, the build
 | |
|  * will fail (on purpose).
 | |
|  *
 | |
|  * If someone wants to fix the code to work properly on such environments, then:
 | |
|  *
 | |
|  * 1) Preconditions should be added to xdrmem_enc functions to make sure the
 | |
|  *    caller doesn't pass arguments which exceed the expected range.
 | |
|  * 2) Functions which take signed integers should be changed to properly do
 | |
|  *    sign extension.
 | |
|  * 3) For ints with less than 32 bits, well.. I suspect you'll have bigger
 | |
|  *    problems than this implementation.
 | |
|  *
 | |
|  * It is also assumed that:
 | |
|  *
 | |
|  * 1) Chars have 8 bits.
 | |
|  * 2) We can always do 32-bit-aligned int memory accesses and byte-aligned
 | |
|  *    memcpy, memset and memcmp.
 | |
|  * 3) Arrays passed to xdr_array() are packed and the compiler/architecture
 | |
|  *    supports element-sized-aligned memory accesses.
 | |
|  * 4) Negative integers are natively stored in two's complement binary
 | |
|  *    representation.
 | |
|  *
 | |
|  * No checks are done for the 4 assumptions above, though.
 | |
|  *
 | |
|  * === Caller expectations ===
 | |
|  *
 | |
|  * Existing documentation does not describe the semantics of XDR operations very
 | |
|  * well.  Therefore, some assumptions about failure semantics will be made and
 | |
|  * will be described below:
 | |
|  *
 | |
|  * 1) If any encoding operation fails (e.g., due to lack of buffer space), the
 | |
|  * the stream should be considered valid only up to the encoding operation
 | |
|  * previous to the one that first failed. However, the stream size as returned
 | |
|  * by xdr_control() cannot be considered to be strictly correct (it may be
 | |
|  * bigger).
 | |
|  *
 | |
|  * Putting it another way, if there is an encoding failure it's undefined
 | |
|  * whether anything is added to the stream in that operation and therefore
 | |
|  * neither xdr_control() nor future encoding operations on the same stream can
 | |
|  * be relied upon to produce correct results.
 | |
|  *
 | |
|  * 2) If a decoding operation fails, it's undefined whether anything will be
 | |
|  * decoded into passed buffers/pointers during that operation, or what the
 | |
|  * values on those buffers will look like.
 | |
|  *
 | |
|  * Future decoding operations on the same stream will also have similar
 | |
|  * undefined behavior.
 | |
|  *
 | |
|  * 3) When the first decoding operation fails it is OK to trust the results of
 | |
|  * previous decoding operations on the same stream, as long as the caller
 | |
|  * expects a failure to be possible (e.g. due to end-of-stream).
 | |
|  *
 | |
|  * However, this is highly discouraged because the caller should know the
 | |
|  * stream size and should be coded to expect any decoding failure to be data
 | |
|  * corruption due to hardware, accidental or even malicious causes, which should
 | |
|  * be handled gracefully in all cases.
 | |
|  *
 | |
|  * In very rare situations where there are strong reasons to believe the data
 | |
|  * can be trusted to be valid and non-tampered with, then the caller may assume
 | |
|  * a decoding failure to be a bug (e.g. due to mismatched data types) and may
 | |
|  * fail non-gracefully.
 | |
|  *
 | |
|  * 4) Non-zero padding bytes will cause the decoding operation to fail.
 | |
|  *
 | |
|  * 5) Zero bytes on string types will also cause the decoding operation to fail.
 | |
|  *
 | |
|  * 6) It is assumed that either the pointer to the stream buffer given by the
 | |
|  * caller is 32-bit aligned or the architecture supports non-32-bit-aligned int
 | |
|  * memory accesses.
 | |
|  *
 | |
|  * 7) The stream buffer and encoding/decoding buffers/ptrs should not overlap.
 | |
|  *
 | |
|  * 8) If a caller passes pointers to non-kernel memory (e.g., pointers to user
 | |
|  * space or MMIO space), the computer may explode.
 | |
|  */
 | |
| 
 | |
| static const struct xdr_ops xdrmem_encode_ops;
 | |
| static const struct xdr_ops xdrmem_decode_ops;
 | |
| 
 | |
| void
 | |
| xdrmem_create(XDR *xdrs, const caddr_t addr, const uint_t size,
 | |
|     const enum xdr_op op)
 | |
| {
 | |
| 	switch (op) {
 | |
| 		case XDR_ENCODE:
 | |
| 			xdrs->x_ops = &xdrmem_encode_ops;
 | |
| 			break;
 | |
| 		case XDR_DECODE:
 | |
| 			xdrs->x_ops = &xdrmem_decode_ops;
 | |
| 			break;
 | |
| 		default:
 | |
| 			xdrs->x_ops = NULL; /* Let the caller know we failed */
 | |
| 			return;
 | |
| 	}
 | |
| 
 | |
| 	xdrs->x_op = op;
 | |
| 	xdrs->x_addr = addr;
 | |
| 	xdrs->x_addr_end = addr + size;
 | |
| 
 | |
| 	if (xdrs->x_addr_end < xdrs->x_addr) {
 | |
| 		xdrs->x_ops = NULL;
 | |
| 	}
 | |
| }
 | |
| EXPORT_SYMBOL(xdrmem_create);
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_control(XDR *xdrs, int req, void *info)
 | |
| {
 | |
| 	struct xdr_bytesrec *rec = (struct xdr_bytesrec *)info;
 | |
| 
 | |
| 	if (req != XDR_GET_BYTES_AVAIL)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	rec->xc_is_last_record = TRUE; /* always TRUE in xdrmem streams */
 | |
| 	rec->xc_num_avail = xdrs->x_addr_end - xdrs->x_addr;
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_enc_bytes(XDR *xdrs, caddr_t cp, const uint_t cnt)
 | |
| {
 | |
| 	uint_t size = roundup(cnt, 4);
 | |
| 	uint_t pad;
 | |
| 
 | |
| 	if (size < cnt)
 | |
| 		return (FALSE); /* Integer overflow */
 | |
| 
 | |
| 	if (xdrs->x_addr > xdrs->x_addr_end)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	if (xdrs->x_addr_end - xdrs->x_addr < size)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	memcpy(xdrs->x_addr, cp, cnt);
 | |
| 
 | |
| 	xdrs->x_addr += cnt;
 | |
| 
 | |
| 	pad = size - cnt;
 | |
| 	if (pad > 0) {
 | |
| 		memset(xdrs->x_addr, 0, pad);
 | |
| 		xdrs->x_addr += pad;
 | |
| 	}
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_dec_bytes(XDR *xdrs, caddr_t cp, const uint_t cnt)
 | |
| {
 | |
| 	static uint32_t zero = 0;
 | |
| 	uint_t size = roundup(cnt, 4);
 | |
| 	uint_t pad;
 | |
| 
 | |
| 	if (size < cnt)
 | |
| 		return (FALSE); /* Integer overflow */
 | |
| 
 | |
| 	if (xdrs->x_addr > xdrs->x_addr_end)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	if (xdrs->x_addr_end - xdrs->x_addr < size)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	memcpy(cp, xdrs->x_addr, cnt);
 | |
| 	xdrs->x_addr += cnt;
 | |
| 
 | |
| 	pad = size - cnt;
 | |
| 	if (pad > 0) {
 | |
| 		/* An inverted memchr() would be useful here... */
 | |
| 		if (memcmp(&zero, xdrs->x_addr, pad) != 0)
 | |
| 			return (FALSE);
 | |
| 
 | |
| 		xdrs->x_addr += pad;
 | |
| 	}
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_enc_uint32(XDR *xdrs, uint32_t val)
 | |
| {
 | |
| 	if (xdrs->x_addr + sizeof (uint32_t) > xdrs->x_addr_end)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	*((uint32_t *)xdrs->x_addr) = cpu_to_be32(val);
 | |
| 
 | |
| 	xdrs->x_addr += sizeof (uint32_t);
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_dec_uint32(XDR *xdrs, uint32_t *val)
 | |
| {
 | |
| 	if (xdrs->x_addr + sizeof (uint32_t) > xdrs->x_addr_end)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	*val = be32_to_cpu(*((uint32_t *)xdrs->x_addr));
 | |
| 
 | |
| 	xdrs->x_addr += sizeof (uint32_t);
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_enc_char(XDR *xdrs, char *cp)
 | |
| {
 | |
| 	uint32_t val;
 | |
| 
 | |
| 	BUILD_BUG_ON(sizeof (char) != 1);
 | |
| 	val = *((unsigned char *) cp);
 | |
| 
 | |
| 	return (xdrmem_enc_uint32(xdrs, val));
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_dec_char(XDR *xdrs, char *cp)
 | |
| {
 | |
| 	uint32_t val;
 | |
| 
 | |
| 	BUILD_BUG_ON(sizeof (char) != 1);
 | |
| 
 | |
| 	if (!xdrmem_dec_uint32(xdrs, &val))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	/*
 | |
| 	 * If any of the 3 other bytes are non-zero then val will be greater
 | |
| 	 * than 0xff and we fail because according to the RFC, this block does
 | |
| 	 * not have a char encoded in it.
 | |
| 	 */
 | |
| 	if (val > 0xff)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	*((unsigned char *) cp) = val;
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_enc_ushort(XDR *xdrs, unsigned short *usp)
 | |
| {
 | |
| 	BUILD_BUG_ON(sizeof (unsigned short) != 2);
 | |
| 
 | |
| 	return (xdrmem_enc_uint32(xdrs, *usp));
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_dec_ushort(XDR *xdrs, unsigned short *usp)
 | |
| {
 | |
| 	uint32_t val;
 | |
| 
 | |
| 	BUILD_BUG_ON(sizeof (unsigned short) != 2);
 | |
| 
 | |
| 	if (!xdrmem_dec_uint32(xdrs, &val))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	/*
 | |
| 	 * Short ints are not in the RFC, but we assume similar logic as in
 | |
| 	 * xdrmem_dec_char().
 | |
| 	 */
 | |
| 	if (val > 0xffff)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	*usp = val;
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_enc_uint(XDR *xdrs, unsigned *up)
 | |
| {
 | |
| 	BUILD_BUG_ON(sizeof (unsigned) != 4);
 | |
| 
 | |
| 	return (xdrmem_enc_uint32(xdrs, *up));
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_dec_uint(XDR *xdrs, unsigned *up)
 | |
| {
 | |
| 	BUILD_BUG_ON(sizeof (unsigned) != 4);
 | |
| 
 | |
| 	return (xdrmem_dec_uint32(xdrs, (uint32_t *)up));
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_enc_ulonglong(XDR *xdrs, u_longlong_t *ullp)
 | |
| {
 | |
| 	BUILD_BUG_ON(sizeof (u_longlong_t) != 8);
 | |
| 
 | |
| 	if (!xdrmem_enc_uint32(xdrs, *ullp >> 32))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	return (xdrmem_enc_uint32(xdrs, *ullp & 0xffffffff));
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdrmem_dec_ulonglong(XDR *xdrs, u_longlong_t *ullp)
 | |
| {
 | |
| 	uint32_t low, high;
 | |
| 
 | |
| 	BUILD_BUG_ON(sizeof (u_longlong_t) != 8);
 | |
| 
 | |
| 	if (!xdrmem_dec_uint32(xdrs, &high))
 | |
| 		return (FALSE);
 | |
| 	if (!xdrmem_dec_uint32(xdrs, &low))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	*ullp = ((u_longlong_t)high << 32) | low;
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdr_enc_array(XDR *xdrs, caddr_t *arrp, uint_t *sizep, const uint_t maxsize,
 | |
|     const uint_t elsize, const xdrproc_t elproc)
 | |
| {
 | |
| 	uint_t i;
 | |
| 	caddr_t addr = *arrp;
 | |
| 
 | |
| 	if (*sizep > maxsize || *sizep > UINT_MAX / elsize)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	if (!xdrmem_enc_uint(xdrs, sizep))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	for (i = 0; i < *sizep; i++) {
 | |
| 		if (!elproc(xdrs, addr))
 | |
| 			return (FALSE);
 | |
| 		addr += elsize;
 | |
| 	}
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdr_dec_array(XDR *xdrs, caddr_t *arrp, uint_t *sizep, const uint_t maxsize,
 | |
|     const uint_t elsize, const xdrproc_t elproc)
 | |
| {
 | |
| 	uint_t i, size;
 | |
| 	bool_t alloc = FALSE;
 | |
| 	caddr_t addr;
 | |
| 
 | |
| 	if (!xdrmem_dec_uint(xdrs, sizep))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	size = *sizep;
 | |
| 
 | |
| 	if (size > maxsize || size > UINT_MAX / elsize)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	/*
 | |
| 	 * The Solaris man page says: "If *arrp is NULL when decoding,
 | |
| 	 * xdr_array() allocates memory and *arrp points to it".
 | |
| 	 */
 | |
| 	if (*arrp == NULL) {
 | |
| 		BUILD_BUG_ON(sizeof (uint_t) > sizeof (size_t));
 | |
| 
 | |
| 		*arrp = kmem_alloc(size * elsize, KM_NOSLEEP);
 | |
| 		if (*arrp == NULL)
 | |
| 			return (FALSE);
 | |
| 
 | |
| 		alloc = TRUE;
 | |
| 	}
 | |
| 
 | |
| 	addr = *arrp;
 | |
| 
 | |
| 	for (i = 0; i < size; i++) {
 | |
| 		if (!elproc(xdrs, addr)) {
 | |
| 			if (alloc)
 | |
| 				kmem_free(*arrp, size * elsize);
 | |
| 			return (FALSE);
 | |
| 		}
 | |
| 		addr += elsize;
 | |
| 	}
 | |
| 
 | |
| 	return (TRUE);
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdr_enc_string(XDR *xdrs, char **sp, const uint_t maxsize)
 | |
| {
 | |
| 	size_t slen = strlen(*sp);
 | |
| 	uint_t len;
 | |
| 
 | |
| 	if (slen > maxsize)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	len = slen;
 | |
| 
 | |
| 	if (!xdrmem_enc_uint(xdrs, &len))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	return (xdrmem_enc_bytes(xdrs, *sp, len));
 | |
| }
 | |
| 
 | |
| static bool_t
 | |
| xdr_dec_string(XDR *xdrs, char **sp, const uint_t maxsize)
 | |
| {
 | |
| 	uint_t size;
 | |
| 	bool_t alloc = FALSE;
 | |
| 
 | |
| 	if (!xdrmem_dec_uint(xdrs, &size))
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	if (size > maxsize || size > UINT_MAX - 1)
 | |
| 		return (FALSE);
 | |
| 
 | |
| 	/*
 | |
| 	 * Solaris man page: "If *sp is NULL when decoding, xdr_string()
 | |
| 	 * allocates memory and *sp points to it".
 | |
| 	 */
 | |
| 	if (*sp == NULL) {
 | |
| 		BUILD_BUG_ON(sizeof (uint_t) > sizeof (size_t));
 | |
| 
 | |
| 		*sp = kmem_alloc(size + 1, KM_NOSLEEP);
 | |
| 		if (*sp == NULL)
 | |
| 			return (FALSE);
 | |
| 
 | |
| 		alloc = TRUE;
 | |
| 	}
 | |
| 
 | |
| 	if (!xdrmem_dec_bytes(xdrs, *sp, size))
 | |
| 		goto fail;
 | |
| 
 | |
| 	if (memchr(*sp, 0, size) != NULL)
 | |
| 		goto fail;
 | |
| 
 | |
| 	(*sp)[size] = '\0';
 | |
| 
 | |
| 	return (TRUE);
 | |
| 
 | |
| fail:
 | |
| 	if (alloc)
 | |
| 		kmem_free(*sp, size + 1);
 | |
| 
 | |
| 	return (FALSE);
 | |
| }
 | |
| 
 | |
| static const struct xdr_ops xdrmem_encode_ops = {
 | |
| 	.xdr_control		= xdrmem_control,
 | |
| 	.xdr_char		= xdrmem_enc_char,
 | |
| 	.xdr_u_short		= xdrmem_enc_ushort,
 | |
| 	.xdr_u_int		= xdrmem_enc_uint,
 | |
| 	.xdr_u_longlong_t	= xdrmem_enc_ulonglong,
 | |
| 	.xdr_opaque		= xdrmem_enc_bytes,
 | |
| 	.xdr_string		= xdr_enc_string,
 | |
| 	.xdr_array		= xdr_enc_array
 | |
| };
 | |
| 
 | |
| static const struct xdr_ops xdrmem_decode_ops = {
 | |
| 	.xdr_control		= xdrmem_control,
 | |
| 	.xdr_char		= xdrmem_dec_char,
 | |
| 	.xdr_u_short		= xdrmem_dec_ushort,
 | |
| 	.xdr_u_int		= xdrmem_dec_uint,
 | |
| 	.xdr_u_longlong_t	= xdrmem_dec_ulonglong,
 | |
| 	.xdr_opaque		= xdrmem_dec_bytes,
 | |
| 	.xdr_string		= xdr_dec_string,
 | |
| 	.xdr_array		= xdr_dec_array
 | |
| };
 |