mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-11-18 02:20:59 +03:00
716154c592
Updated AUTHORS, COPYING, DISCLAIMER, and INSTALL files. Added standardized headers to all source file to clearly indicate the copyright, license, and to give credit where credit is due.
521 lines
13 KiB
C
521 lines
13 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.
|
|
* For details, see <http://github.com/behlendorf/spl/>.
|
|
*
|
|
* 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 <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 struct xdr_ops xdrmem_encode_ops;
|
|
static 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:
|
|
CWARN("Invalid op value: %d\n", op);
|
|
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) {
|
|
CWARN("Overflow while creating xdrmem: %p, %u\n", addr, size);
|
|
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) {
|
|
CWARN("Called with unknown request: %d\n", req);
|
|
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 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 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
|
|
};
|
|
|