| /* |
| * 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://zfsonlinux.org/>. |
| * |
| * 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/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: |
| 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 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 |
| }; |