1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
27/*	  All Rights Reserved  	*/
28
29/*
30 * Portions of this source code were derived from Berkeley 4.3 BSD
31 * under license from the Regents of the University of California.
32 */
33
34/*
35 * xdr_mblk.c, XDR implementation on kernel streams mblks.
36 */
37
38#include <sys/param.h>
39#include <sys/types.h>
40#include <sys/systm.h>
41#include <sys/stream.h>
42#include <sys/cmn_err.h>
43#include <sys/strsubr.h>
44#include <sys/strsun.h>
45#include <sys/debug.h>
46#include <sys/sysmacros.h>
47
48#include <rpc/types.h>
49#include <rpc/xdr.h>
50
51static bool_t	xdrmblk_getint32(XDR *, int32_t *);
52static bool_t	xdrmblk_putint32(XDR *, int32_t *);
53static bool_t	xdrmblk_getbytes(XDR *, caddr_t, int);
54static bool_t	xdrmblk_putbytes(XDR *, caddr_t, int);
55static uint_t	xdrmblk_getpos(XDR *);
56static bool_t	xdrmblk_setpos(XDR *, uint_t);
57static rpc_inline_t *xdrmblk_inline(XDR *, int);
58static void	xdrmblk_destroy(XDR *);
59static bool_t	xdrmblk_control(XDR *, int, void *);
60
61static mblk_t *xdrmblk_alloc(int);
62
63/*
64 * Xdr on mblks operations vector.
65 */
66struct	xdr_ops xdrmblk_ops = {
67	xdrmblk_getbytes,
68	xdrmblk_putbytes,
69	xdrmblk_getpos,
70	xdrmblk_setpos,
71	xdrmblk_inline,
72	xdrmblk_destroy,
73	xdrmblk_control,
74	xdrmblk_getint32,
75	xdrmblk_putint32
76};
77
78/*
79 * Initialize xdr stream.
80 */
81void
82xdrmblk_init(XDR *xdrs, mblk_t *m, enum xdr_op op, int sz)
83{
84	xdrs->x_op = op;
85	xdrs->x_ops = &xdrmblk_ops;
86	xdrs->x_base = (caddr_t)m;
87	xdrs->x_public = NULL;
88	xdrs->x_private = (caddr_t)(uintptr_t)sz;
89
90	if (op == XDR_DECODE)
91		xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
92	else
93		xdrs->x_handy = (int)(m->b_datap->db_lim - m->b_datap->db_base);
94}
95
96/* ARGSUSED */
97static void
98xdrmblk_destroy(XDR *xdrs)
99{
100}
101
102static bool_t
103xdrmblk_getint32(XDR *xdrs, int32_t *int32p)
104{
105	mblk_t *m;
106
107	/* LINTED pointer alignment */
108	m = (mblk_t *)xdrs->x_base;
109	if (m == NULL)
110		return (FALSE);
111	/*
112	 * If the pointer is not aligned or there is not
113	 * enough bytes, pullupmsg to get enough bytes and
114	 * align the mblk.
115	 */
116	if (!IS_P2ALIGNED(m->b_rptr, sizeof (int32_t)) ||
117	    xdrs->x_handy < sizeof (int32_t)) {
118		while (!pullupmsg(m, sizeof (int32_t))) {
119			/*
120			 * Could have failed due to not
121			 * enough data or an allocb failure.
122			 */
123			if (xmsgsize(m) < sizeof (int32_t))
124				return (FALSE);
125			delay(hz);
126		}
127		xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
128	}
129
130	/* LINTED pointer alignment */
131	*int32p = ntohl(*((int32_t *)(m->b_rptr)));
132	m->b_rptr += sizeof (int32_t);
133
134	/*
135	 * Instead of leaving handy as 0 causing more pullupmsg's
136	 * simply move to the next mblk.
137	 */
138	if ((xdrs->x_handy -= sizeof (int32_t)) == 0) {
139		m = m->b_cont;
140		xdrs->x_base = (caddr_t)m;
141		if (m != NULL)
142			xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
143	}
144	return (TRUE);
145}
146
147static bool_t
148xdrmblk_putint32(XDR *xdrs, int32_t *int32p)
149{
150	mblk_t *m;
151
152	/* LINTED pointer alignment */
153	m = (mblk_t *)xdrs->x_base;
154	if (m == NULL)
155		return (FALSE);
156	if ((xdrs->x_handy -= (int)sizeof (int32_t)) < 0) {
157		if (m->b_cont == NULL) {
158			m->b_cont = xdrmblk_alloc((int)(uintptr_t)
159			    xdrs->x_private);
160		}
161		m = m->b_cont;
162		xdrs->x_base = (caddr_t)m;
163		if (m == NULL) {
164			xdrs->x_handy = 0;
165			return (FALSE);
166		}
167		xdrs->x_handy = (int)(m->b_datap->db_lim - m->b_rptr -
168		    sizeof (int32_t));
169		ASSERT(m->b_rptr == m->b_wptr);
170		ASSERT(m->b_rptr >= m->b_datap->db_base);
171		ASSERT(m->b_rptr < m->b_datap->db_lim);
172	}
173	/* LINTED pointer alignment */
174	*(int32_t *)m->b_wptr = htonl(*int32p);
175	m->b_wptr += sizeof (int32_t);
176	ASSERT(m->b_wptr <= m->b_datap->db_lim);
177	return (TRUE);
178}
179
180/*
181 * We pick 16 as a compromise threshold for most architectures.
182 */
183#define	XDRMBLK_BCOPY_LIMIT	16
184
185static bool_t
186xdrmblk_getbytes(XDR *xdrs, caddr_t addr, int len)
187{
188	mblk_t *m;
189	int i;
190
191	/* LINTED pointer alignment */
192	m = (mblk_t *)xdrs->x_base;
193	if (m == NULL)
194		return (FALSE);
195	/*
196	 * Performance tweak: converted explicit bcopy()
197	 * call to simple in-line. This function is called
198	 * to process things like readdir reply filenames
199	 * which are small strings--typically 12 bytes or less.
200	 * Overhead of calling bcopy() is obnoxious for such
201	 * small copies.
202	 */
203	while ((xdrs->x_handy -= len) < 0) {
204		if ((xdrs->x_handy += len) > 0) {
205			if (len < XDRMBLK_BCOPY_LIMIT) {
206				for (i = 0; i < xdrs->x_handy; i++)
207					*addr++ = *m->b_rptr++;
208			} else {
209				bcopy(m->b_rptr, addr, xdrs->x_handy);
210				m->b_rptr += xdrs->x_handy;
211				addr += xdrs->x_handy;
212			}
213			len -= xdrs->x_handy;
214		}
215		m = m->b_cont;
216		xdrs->x_base = (caddr_t)m;
217		if (m == NULL) {
218			xdrs->x_handy = 0;
219			return (FALSE);
220		}
221		xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
222	}
223	if (len < XDRMBLK_BCOPY_LIMIT) {
224		for (i = 0; i < len; i++)
225			*addr++ = *m->b_rptr++;
226	} else {
227		bcopy(m->b_rptr, addr, len);
228		m->b_rptr += len;
229	}
230	return (TRUE);
231}
232
233/*
234 * Sort of like getbytes except that instead of getting bytes we return the
235 * mblk chain which contains the data.  If the data ends in the middle of
236 * an mblk, the mblk is dup'd and split, so that the data will end on an
237 * mblk.  Note that it is up to the caller to keep track of the data length
238 * and not walk too far down the mblk chain.
239 */
240
241bool_t
242xdrmblk_getmblk(XDR *xdrs, mblk_t **mm, uint_t *lenp)
243{
244	mblk_t *m, *nextm;
245	int len;
246	int32_t llen;
247
248	if (!xdrmblk_getint32(xdrs, &llen))
249		return (FALSE);
250
251	*lenp = llen;
252	/* LINTED pointer alignment */
253	m = (mblk_t *)xdrs->x_base;
254	*mm = m;
255
256	/*
257	 * Walk the mblk chain until we get to the end or we've gathered
258	 * enough data.
259	 */
260	len = 0;
261	llen = roundup(llen, BYTES_PER_XDR_UNIT);
262	while (m != NULL && len + (int)MBLKL(m) <= llen) {
263		len += (int)MBLKL(m);
264		m = m->b_cont;
265	}
266	if (len < llen) {
267		if (m == NULL) {
268			return (FALSE);
269		} else {
270			int tail_bytes = llen - len;
271
272			/*
273			 * Split the mblk with the last chunk of data and
274			 * insert it into the chain.  The new mblk goes
275			 * after the existing one so that it will get freed
276			 * properly.
277			 */
278			nextm = dupb(m);
279			if (nextm == NULL)
280				return (FALSE);
281			nextm->b_cont = m->b_cont;
282			m->b_cont = nextm;
283			m->b_wptr = m->b_rptr + tail_bytes;
284			nextm->b_rptr += tail_bytes;
285			ASSERT(nextm->b_rptr != nextm->b_wptr);
286
287			m = nextm;	/* for x_base */
288		}
289	}
290	xdrs->x_base = (caddr_t)m;
291	xdrs->x_handy = m != NULL ? MBLKL(m) : 0;
292	return (TRUE);
293}
294
295static bool_t
296xdrmblk_putbytes(XDR *xdrs, caddr_t addr, int len)
297{
298	mblk_t *m;
299	uint_t i;
300
301	/* LINTED pointer alignment */
302	m = (mblk_t *)xdrs->x_base;
303	if (m == NULL)
304		return (FALSE);
305	/*
306	 * Performance tweak: converted explicit bcopy()
307	 * call to simple in-line. This function is called
308	 * to process things like readdir reply filenames
309	 * which are small strings--typically 12 bytes or less.
310	 * Overhead of calling bcopy() is obnoxious for such
311	 * small copies.
312	 */
313	while ((xdrs->x_handy -= len) < 0) {
314		if ((xdrs->x_handy += len) > 0) {
315			if (xdrs->x_handy < XDRMBLK_BCOPY_LIMIT) {
316				for (i = 0; i < (uint_t)xdrs->x_handy; i++)
317					*m->b_wptr++ = *addr++;
318			} else {
319				bcopy(addr, m->b_wptr, xdrs->x_handy);
320				m->b_wptr += xdrs->x_handy;
321				addr += xdrs->x_handy;
322			}
323			len -= xdrs->x_handy;
324		}
325
326		/*
327		 * We don't have enough space, so allocate the
328		 * amount we need, or x_private, whichever is larger.
329		 * It is better to let the underlying transport divide
330		 * large chunks than to try and guess what is best.
331		 */
332		if (m->b_cont == NULL)
333			m->b_cont = xdrmblk_alloc(MAX(len,
334			    (int)(uintptr_t)xdrs->x_private));
335
336		m = m->b_cont;
337		xdrs->x_base = (caddr_t)m;
338		if (m == NULL) {
339			xdrs->x_handy = 0;
340			return (FALSE);
341		}
342		xdrs->x_handy = (int)(m->b_datap->db_lim - m->b_rptr);
343		ASSERT(m->b_rptr == m->b_wptr);
344		ASSERT(m->b_rptr >= m->b_datap->db_base);
345		ASSERT(m->b_rptr < m->b_datap->db_lim);
346	}
347	if (len < XDRMBLK_BCOPY_LIMIT) {
348		for (i = 0; i < len; i++)
349			*m->b_wptr++ = *addr++;
350	} else {
351		bcopy(addr, m->b_wptr, len);
352		m->b_wptr += len;
353	}
354	ASSERT(m->b_wptr <= m->b_datap->db_lim);
355	return (TRUE);
356}
357
358/*
359 * We avoid a copy by merely adding this mblk to the list.  The caller is
360 * responsible for allocating and filling in the mblk. If len is
361 * not a multiple of BYTES_PER_XDR_UNIT, the caller has the option
362 * of making the data a BYTES_PER_XDR_UNIT multiple (b_wptr - b_rptr is
363 * a BYTES_PER_XDR_UNIT multiple), but in this case the caller has to ensure
364 * that the filler bytes are initialized to zero.
365 */
366bool_t
367xdrmblk_putmblk(XDR *xdrs, mblk_t *m, uint_t len)
368{
369	int32_t llen = (int32_t)len;
370
371	if ((DLEN(m) % BYTES_PER_XDR_UNIT) != 0)
372		return (FALSE);
373	if (!xdrmblk_putint32(xdrs, &llen))
374		return (FALSE);
375
376	/* LINTED pointer alignment */
377	((mblk_t *)xdrs->x_base)->b_cont = m;
378
379	/* base points to the last mblk */
380	while (m->b_cont)
381		m = m->b_cont;
382	xdrs->x_base = (caddr_t)m;
383	xdrs->x_handy = 0;
384	return (TRUE);
385}
386
387static uint_t
388xdrmblk_getpos(XDR *xdrs)
389{
390	uint_t tmp;
391	mblk_t *m;
392
393	/* LINTED pointer alignment */
394	m = (mblk_t *)xdrs->x_base;
395
396	if (xdrs->x_op == XDR_DECODE)
397		tmp = (uint_t)(m->b_rptr - m->b_datap->db_base);
398	else
399		tmp = (uint_t)(m->b_wptr - m->b_datap->db_base);
400	return (tmp);
401
402}
403
404static bool_t
405xdrmblk_setpos(XDR *xdrs, uint_t pos)
406{
407	mblk_t *m;
408	unsigned char *newaddr;
409
410	/* LINTED pointer alignment */
411	m = (mblk_t *)xdrs->x_base;
412	if (m == NULL)
413		return (FALSE);
414
415	/* calculate the new address from the base */
416	newaddr = m->b_datap->db_base + pos;
417
418	if (xdrs->x_op == XDR_DECODE) {
419		if (newaddr > m->b_wptr)
420			return (FALSE);
421		m->b_rptr = newaddr;
422		xdrs->x_handy = (int)(m->b_wptr - newaddr);
423	} else {
424		if (newaddr > m->b_datap->db_lim)
425			return (FALSE);
426		m->b_wptr = newaddr;
427		xdrs->x_handy = (int)(m->b_datap->db_lim - newaddr);
428	}
429
430	return (TRUE);
431}
432
433#ifdef DEBUG
434static int xdrmblk_inline_hits = 0;
435static int xdrmblk_inline_misses = 0;
436static int do_xdrmblk_inline = 1;
437#endif
438
439static rpc_inline_t *
440xdrmblk_inline(XDR *xdrs, int len)
441{
442	rpc_inline_t *buf;
443	mblk_t *m;
444
445	/*
446	 * Can't inline XDR_FREE calls, doesn't make sense.
447	 */
448	if (xdrs->x_op == XDR_FREE)
449		return (NULL);
450
451	/*
452	 * Can't inline if there isn't enough room, don't have an
453	 * mblk pointer, its not 4 byte aligned, or if there is more than
454	 * one reference to the data block associated with this mblk.  This last
455	 * check is used because the caller may want to modified
456	 * the data in the inlined portion and someone else is
457	 * holding a reference to the data who may not want it
458	 * to be modified.
459	 */
460	if (xdrs->x_handy < len ||
461	    /* LINTED pointer alignment */
462	    (m = (mblk_t *)xdrs->x_base) == NULL ||
463	    !IS_P2ALIGNED(m->b_rptr, sizeof (int32_t)) ||
464	    m->b_datap->db_ref != 1) {
465#ifdef DEBUG
466		xdrmblk_inline_misses++;
467#endif
468		return (NULL);
469	}
470
471#ifdef DEBUG
472	if (!do_xdrmblk_inline) {
473		xdrmblk_inline_misses++;
474		return (NULL);
475	}
476#endif
477
478	xdrs->x_handy -= len;
479	if (xdrs->x_op == XDR_DECODE) {
480		/* LINTED pointer alignment */
481		buf = (rpc_inline_t *)m->b_rptr;
482		m->b_rptr += len;
483	} else {
484		/* LINTED pointer alignment */
485		buf = (rpc_inline_t *)m->b_wptr;
486		m->b_wptr += len;
487	}
488#ifdef DEBUG
489	xdrmblk_inline_hits++;
490#endif
491	return (buf);
492}
493
494static bool_t
495xdrmblk_control(XDR *xdrs, int request, void *info)
496{
497	mblk_t *m;
498	int32_t *int32p;
499	int len;
500
501	switch (request) {
502	case XDR_PEEK:
503		/*
504		 * Return the next 4 byte unit in the XDR stream.
505		 */
506		if (xdrs->x_handy < sizeof (int32_t))
507			return (FALSE);
508
509		/* LINTED pointer alignment */
510		m = (mblk_t *)xdrs->x_base;
511		if (m == NULL)
512			return (FALSE);
513
514		/*
515		 * If the pointer is not aligned, fail the peek
516		 */
517		if (!IS_P2ALIGNED(m->b_rptr, sizeof (int32_t)))
518			return (FALSE);
519
520		int32p = (int32_t *)info;
521		/* LINTED pointer alignment */
522		*int32p = ntohl(*((int32_t *)(m->b_rptr)));
523		return (TRUE);
524
525	case XDR_SKIPBYTES:
526		/* LINTED pointer alignment */
527		m = (mblk_t *)xdrs->x_base;
528		if (m == NULL)
529			return (FALSE);
530		int32p = (int32_t *)info;
531		len = RNDUP((int)(*int32p));
532		if (len < 0)
533			return (FALSE);
534		while ((xdrs->x_handy -= len) < 0) {
535			if ((xdrs->x_handy += len) > 0) {
536				m->b_rptr += xdrs->x_handy;
537				len -= xdrs->x_handy;
538			}
539			m = m->b_cont;
540			xdrs->x_base = (caddr_t)m;
541			if (m == NULL) {
542				xdrs->x_handy = 0;
543				return (FALSE);
544			}
545			xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
546		}
547		m->b_rptr += len;
548		return (TRUE);
549
550	default:
551		return (FALSE);
552	}
553}
554
555#define	HDR_SPACE	128
556
557static mblk_t *
558xdrmblk_alloc(int sz)
559{
560	mblk_t *mp;
561
562	if (sz == 0)
563		return (NULL);
564
565	/*
566	 * Pad the front of the message to allow the lower networking
567	 * layers space to add headers as needed.
568	 */
569	sz += HDR_SPACE;
570
571	while ((mp = allocb(sz, BPRI_LO)) == NULL) {
572		if (strwaitbuf(sz, BPRI_LO))
573			return (NULL);
574	}
575
576	mp->b_wptr += HDR_SPACE;
577	mp->b_rptr = mp->b_wptr;
578
579	return (mp);
580}
581