xref: /illumos-gate/usr/src/uts/common/rpc/xdr_mblk.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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 2008 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 
51 static bool_t	xdrmblk_getint32(XDR *, int32_t *);
52 static bool_t	xdrmblk_putint32(XDR *, int32_t *);
53 static bool_t	xdrmblk_getbytes(XDR *, caddr_t, int);
54 static bool_t	xdrmblk_putbytes(XDR *, caddr_t, int);
55 static uint_t	xdrmblk_getpos(XDR *);
56 static bool_t	xdrmblk_setpos(XDR *, uint_t);
57 static rpc_inline_t *xdrmblk_inline(XDR *, int);
58 static void	xdrmblk_destroy(XDR *);
59 static bool_t	xdrmblk_control(XDR *, int, void *);
60 
61 static mblk_t *xdrmblk_alloc(int);
62 
63 /*
64  * Xdr on mblks operations vector.
65  */
66 struct	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  */
81 void
82 xdrmblk_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 */
97 static void
98 xdrmblk_destroy(XDR *xdrs)
99 {
100 }
101 
102 static bool_t
103 xdrmblk_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 
147 static bool_t
148 xdrmblk_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 
185 static bool_t
186 xdrmblk_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 
241 bool_t
242 xdrmblk_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 
295 static bool_t
296 xdrmblk_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. Note: Doesn't to work for
365  * chained mblks.
366  */
367 bool_t
368 xdrmblk_putmblk(XDR *xdrs, mblk_t *m, uint_t len)
369 {
370 	int32_t llen = (int32_t)len;
371 
372 	if (((m->b_wptr - m->b_rptr) % BYTES_PER_XDR_UNIT) != 0)
373 		return (FALSE);
374 	if (!xdrmblk_putint32(xdrs, &llen))
375 		return (FALSE);
376 	/* LINTED pointer alignment */
377 	((mblk_t *)xdrs->x_base)->b_cont = m;
378 	xdrs->x_base = (caddr_t)m;
379 	xdrs->x_handy = 0;
380 	return (TRUE);
381 }
382 
383 static uint_t
384 xdrmblk_getpos(XDR *xdrs)
385 {
386 	uint_t tmp;
387 	mblk_t *m;
388 
389 	/* LINTED pointer alignment */
390 	m = (mblk_t *)xdrs->x_base;
391 
392 	if (xdrs->x_op == XDR_DECODE)
393 		tmp = (uint_t)(m->b_rptr - m->b_datap->db_base);
394 	else
395 		tmp = (uint_t)(m->b_wptr - m->b_datap->db_base);
396 	return (tmp);
397 
398 }
399 
400 static bool_t
401 xdrmblk_setpos(XDR *xdrs, uint_t pos)
402 {
403 	mblk_t *m;
404 	unsigned char *newaddr;
405 
406 	/* LINTED pointer alignment */
407 	m = (mblk_t *)xdrs->x_base;
408 	if (m == NULL)
409 		return (FALSE);
410 
411 	/* calculate the new address from the base */
412 	newaddr = m->b_datap->db_base + pos;
413 
414 	if (xdrs->x_op == XDR_DECODE) {
415 		if (newaddr > m->b_wptr)
416 			return (FALSE);
417 		m->b_rptr = newaddr;
418 		xdrs->x_handy = (int)(m->b_wptr - newaddr);
419 	} else {
420 		if (newaddr > m->b_datap->db_lim)
421 			return (FALSE);
422 		m->b_wptr = newaddr;
423 		xdrs->x_handy = (int)(m->b_datap->db_lim - newaddr);
424 	}
425 
426 	return (TRUE);
427 }
428 
429 #ifdef DEBUG
430 static int xdrmblk_inline_hits = 0;
431 static int xdrmblk_inline_misses = 0;
432 static int do_xdrmblk_inline = 1;
433 #endif
434 
435 static rpc_inline_t *
436 xdrmblk_inline(XDR *xdrs, int len)
437 {
438 	rpc_inline_t *buf;
439 	mblk_t *m;
440 
441 	/*
442 	 * Can't inline XDR_FREE calls, doesn't make sense.
443 	 */
444 	if (xdrs->x_op == XDR_FREE)
445 		return (NULL);
446 
447 	/*
448 	 * Can't inline if there isn't enough room, don't have an
449 	 * mblk pointer, its not 4 byte aligned, or if there is more than
450 	 * one reference to the data block associated with this mblk.  This last
451 	 * check is used because the caller may want to modified
452 	 * the data in the inlined portion and someone else is
453 	 * holding a reference to the data who may not want it
454 	 * to be modified.
455 	 */
456 	if (xdrs->x_handy < len ||
457 	    /* LINTED pointer alignment */
458 	    (m = (mblk_t *)xdrs->x_base) == NULL ||
459 	    !IS_P2ALIGNED(m->b_rptr, sizeof (int32_t)) ||
460 	    m->b_datap->db_ref != 1) {
461 #ifdef DEBUG
462 		xdrmblk_inline_misses++;
463 #endif
464 		return (NULL);
465 	}
466 
467 #ifdef DEBUG
468 	if (!do_xdrmblk_inline) {
469 		xdrmblk_inline_misses++;
470 		return (NULL);
471 	}
472 #endif
473 
474 	xdrs->x_handy -= len;
475 	if (xdrs->x_op == XDR_DECODE) {
476 		/* LINTED pointer alignment */
477 		buf = (rpc_inline_t *)m->b_rptr;
478 		m->b_rptr += len;
479 	} else {
480 		/* LINTED pointer alignment */
481 		buf = (rpc_inline_t *)m->b_wptr;
482 		m->b_wptr += len;
483 	}
484 #ifdef DEBUG
485 	xdrmblk_inline_hits++;
486 #endif
487 	return (buf);
488 }
489 
490 static bool_t
491 xdrmblk_control(XDR *xdrs, int request, void *info)
492 {
493 	mblk_t *m;
494 	int32_t *int32p;
495 	int len;
496 
497 	switch (request) {
498 	case XDR_PEEK:
499 		/*
500 		 * Return the next 4 byte unit in the XDR stream.
501 		 */
502 		if (xdrs->x_handy < sizeof (int32_t))
503 			return (FALSE);
504 
505 		/* LINTED pointer alignment */
506 		m = (mblk_t *)xdrs->x_base;
507 		if (m == NULL)
508 			return (FALSE);
509 
510 		/*
511 		 * If the pointer is not aligned, fail the peek
512 		 */
513 		if (!IS_P2ALIGNED(m->b_rptr, sizeof (int32_t)))
514 			return (FALSE);
515 
516 		int32p = (int32_t *)info;
517 		/* LINTED pointer alignment */
518 		*int32p = ntohl(*((int32_t *)(m->b_rptr)));
519 		return (TRUE);
520 
521 	case XDR_SKIPBYTES:
522 		/* LINTED pointer alignment */
523 		m = (mblk_t *)xdrs->x_base;
524 		if (m == NULL)
525 			return (FALSE);
526 		int32p = (int32_t *)info;
527 		len = RNDUP((int)(*int32p));
528 		if (len < 0)
529 			return (FALSE);
530 		while ((xdrs->x_handy -= len) < 0) {
531 			if ((xdrs->x_handy += len) > 0) {
532 				m->b_rptr += xdrs->x_handy;
533 				len -= xdrs->x_handy;
534 			}
535 			m = m->b_cont;
536 			xdrs->x_base = (caddr_t)m;
537 			if (m == NULL) {
538 				xdrs->x_handy = 0;
539 				return (FALSE);
540 			}
541 			xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
542 		}
543 		m->b_rptr += len;
544 		return (TRUE);
545 
546 	default:
547 		return (FALSE);
548 	}
549 }
550 
551 #define	HDR_SPACE	128
552 
553 static mblk_t *
554 xdrmblk_alloc(int sz)
555 {
556 	mblk_t *mp;
557 
558 	if (sz == 0)
559 		return (NULL);
560 
561 	/*
562 	 * Pad the front of the message to allow the lower networking
563 	 * layers space to add headers as needed.
564 	 */
565 	sz += HDR_SPACE;
566 
567 	while ((mp = allocb(sz, BPRI_LO)) == NULL) {
568 		if (strwaitbuf(sz, BPRI_LO))
569 			return (NULL);
570 	}
571 
572 	mp->b_wptr += HDR_SPACE;
573 	mp->b_rptr = mp->b_wptr;
574 
575 	return (mp);
576 }
577