xref: /illumos-gate/usr/src/uts/common/rpc/xdr_mblk.c (revision ba2be53024c0b999e74ba9adcd7d80fec5df8c57)
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 2006 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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
35 
36 /*
37  * xdr_mblk.c, XDR implementation on kernel streams mblks.
38  */
39 
40 #include <sys/param.h>
41 #include <sys/types.h>
42 #include <sys/systm.h>
43 #include <sys/stream.h>
44 #include <sys/cmn_err.h>
45 #include <sys/strsubr.h>
46 #include <sys/strsun.h>
47 #include <sys/debug.h>
48 #include <sys/sysmacros.h>
49 
50 #include <rpc/types.h>
51 #include <rpc/xdr.h>
52 
53 static bool_t	xdrmblk_getint32(XDR *, int32_t *);
54 static bool_t	xdrmblk_putint32(XDR *, int32_t *);
55 static bool_t	xdrmblk_getbytes(XDR *, caddr_t, int);
56 static bool_t	xdrmblk_putbytes(XDR *, caddr_t, int);
57 static uint_t	xdrmblk_getpos(XDR *);
58 static bool_t	xdrmblk_setpos(XDR *, uint_t);
59 static rpc_inline_t *xdrmblk_inline(XDR *, int);
60 static void	xdrmblk_destroy(XDR *);
61 static bool_t	xdrmblk_control(XDR *, int, void *);
62 
63 static mblk_t *xdrmblk_alloc(int);
64 
65 /*
66  * Xdr on mblks operations vector.
67  */
68 struct	xdr_ops xdrmblk_ops = {
69 	xdrmblk_getbytes,
70 	xdrmblk_putbytes,
71 	xdrmblk_getpos,
72 	xdrmblk_setpos,
73 	xdrmblk_inline,
74 	xdrmblk_destroy,
75 	xdrmblk_control,
76 	xdrmblk_getint32,
77 	xdrmblk_putint32
78 };
79 
80 /*
81  * Initialize xdr stream.
82  */
83 void
84 xdrmblk_init(XDR *xdrs, mblk_t *m, enum xdr_op op, int sz)
85 {
86 	xdrs->x_op = op;
87 	xdrs->x_ops = &xdrmblk_ops;
88 	xdrs->x_base = (caddr_t)m;
89 	xdrs->x_public = NULL;
90 	xdrs->x_private = (caddr_t)(uintptr_t)sz;
91 
92 	if (op == XDR_DECODE)
93 		xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
94 	else
95 		xdrs->x_handy = (int)(m->b_datap->db_lim - m->b_datap->db_base);
96 }
97 
98 /* ARGSUSED */
99 static void
100 xdrmblk_destroy(XDR *xdrs)
101 {
102 }
103 
104 static bool_t
105 xdrmblk_getint32(XDR *xdrs, int32_t *int32p)
106 {
107 	mblk_t *m;
108 
109 	/* LINTED pointer alignment */
110 	m = (mblk_t *)xdrs->x_base;
111 	if (m == NULL)
112 		return (FALSE);
113 	/*
114 	 * If the pointer is not aligned or there is not
115 	 * enough bytes, pullupmsg to get enough bytes and
116 	 * align the mblk.
117 	 */
118 	if (!IS_P2ALIGNED(m->b_rptr, sizeof (int32_t)) ||
119 			xdrs->x_handy < sizeof (int32_t)) {
120 		while (!pullupmsg(m, sizeof (int32_t))) {
121 			/*
122 			 * Could have failed due to not
123 			 * enough data or an allocb failure.
124 			 */
125 			if (xmsgsize(m) < sizeof (int32_t))
126 				return (FALSE);
127 			delay(hz);
128 		}
129 		xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
130 	}
131 
132 	/* LINTED pointer alignment */
133 	*int32p = ntohl(*((int32_t *)(m->b_rptr)));
134 	m->b_rptr += sizeof (int32_t);
135 
136 	/*
137 	 * Instead of leaving handy as 0 causing more pullupmsg's
138 	 * simply move to the next mblk.
139 	 */
140 	if ((xdrs->x_handy -= sizeof (int32_t)) == 0) {
141 		m = m->b_cont;
142 		xdrs->x_base = (caddr_t)m;
143 		if (m != NULL)
144 			xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
145 	}
146 	return (TRUE);
147 }
148 
149 static bool_t
150 xdrmblk_putint32(XDR *xdrs, int32_t *int32p)
151 {
152 	mblk_t *m;
153 
154 	/* LINTED pointer alignment */
155 	m = (mblk_t *)xdrs->x_base;
156 	if (m == NULL)
157 		return (FALSE);
158 	if ((xdrs->x_handy -= (int)sizeof (int32_t)) < 0) {
159 		if (m->b_cont == NULL) {
160 			m->b_cont = xdrmblk_alloc((int)(uintptr_t)
161 			    xdrs->x_private);
162 		}
163 		m = m->b_cont;
164 		xdrs->x_base = (caddr_t)m;
165 		if (m == NULL) {
166 			xdrs->x_handy = 0;
167 			return (FALSE);
168 		}
169 		xdrs->x_handy = (int)(m->b_datap->db_lim - m->b_rptr -
170 			    sizeof (int32_t));
171 		ASSERT(m->b_rptr == m->b_wptr);
172 		ASSERT(m->b_rptr >= m->b_datap->db_base);
173 		ASSERT(m->b_rptr < m->b_datap->db_lim);
174 	}
175 	/* LINTED pointer alignment */
176 	*(int32_t *)m->b_wptr = htonl(*int32p);
177 	m->b_wptr += sizeof (int32_t);
178 	ASSERT(m->b_wptr <= m->b_datap->db_lim);
179 	return (TRUE);
180 }
181 
182 /*
183  * We pick 16 as a compromise threshold for most architectures.
184  */
185 #define	XDRMBLK_BCOPY_LIMIT	16
186 
187 static bool_t
188 xdrmblk_getbytes(XDR *xdrs, caddr_t addr, int len)
189 {
190 	mblk_t *m;
191 	int i;
192 
193 	/* LINTED pointer alignment */
194 	m = (mblk_t *)xdrs->x_base;
195 	if (m == NULL)
196 		return (FALSE);
197 	/*
198 	 * Performance tweak: converted explicit bcopy()
199 	 * call to simple in-line. This function is called
200 	 * to process things like readdir reply filenames
201 	 * which are small strings--typically 12 bytes or less.
202 	 * Overhead of calling bcopy() is obnoxious for such
203 	 * small copies.
204 	 */
205 	while ((xdrs->x_handy -= len) < 0) {
206 		if ((xdrs->x_handy += len) > 0) {
207 			if (len < XDRMBLK_BCOPY_LIMIT) {
208 				for (i = 0; i < xdrs->x_handy; i++)
209 					*addr++ = *m->b_rptr++;
210 			} else {
211 				bcopy(m->b_rptr, addr, xdrs->x_handy);
212 				m->b_rptr += xdrs->x_handy;
213 				addr += xdrs->x_handy;
214 			}
215 			len -= xdrs->x_handy;
216 		}
217 		m = m->b_cont;
218 		xdrs->x_base = (caddr_t)m;
219 		if (m == NULL) {
220 			xdrs->x_handy = 0;
221 			return (FALSE);
222 		}
223 		xdrs->x_handy = (int)(m->b_wptr - m->b_rptr);
224 	}
225 	if (len < XDRMBLK_BCOPY_LIMIT) {
226 		for (i = 0; i < len; i++)
227 			*addr++ = *m->b_rptr++;
228 	} else {
229 		bcopy(m->b_rptr, addr, len);
230 		m->b_rptr += len;
231 	}
232 	return (TRUE);
233 }
234 
235 /*
236  * Sort of like getbytes except that instead of getting bytes we return the
237  * mblk chain which contains the data.  If the data ends in the middle of
238  * an mblk, the mblk is dup'd and split, so that the data will end on an
239  * mblk.  Note that it is up to the caller to keep track of the data length
240  * and not walk too far down the mblk chain.
241  */
242 
243 bool_t
244 xdrmblk_getmblk(XDR *xdrs, mblk_t **mm, uint_t *lenp)
245 {
246 	mblk_t *m, *nextm;
247 	int len;
248 	int32_t llen;
249 
250 	if (!xdrmblk_getint32(xdrs, &llen))
251 		return (FALSE);
252 
253 	*lenp = llen;
254 	/* LINTED pointer alignment */
255 	m = (mblk_t *)xdrs->x_base;
256 	*mm = m;
257 
258 	/*
259 	 * Walk the mblk chain until we get to the end or we've gathered
260 	 * enough data.
261 	 */
262 	len = 0;
263 	llen = roundup(llen, BYTES_PER_XDR_UNIT);
264 	while (m != NULL && len + (int)MBLKL(m) <= llen) {
265 		len += (int)MBLKL(m);
266 		m = m->b_cont;
267 	}
268 	if (len < llen) {
269 		if (m == NULL) {
270 			/* not enough data in XDR stream */
271 			printf("xdrmblk_getmblk failed\n");
272 			return (FALSE);
273 		} else {
274 			int tail_bytes = llen - len;
275 
276 			/*
277 			 * Split the mblk with the last chunk of data and
278 			 * insert it into the chain.  The new mblk goes
279 			 * after the existing one so that it will get freed
280 			 * properly.
281 			 */
282 			nextm = dupb(m);
283 			if (nextm == NULL)
284 				return (FALSE);
285 			nextm->b_cont = m->b_cont;
286 			m->b_cont = nextm;
287 			m->b_wptr = m->b_rptr + tail_bytes;
288 			nextm->b_rptr += tail_bytes;
289 			ASSERT(nextm->b_rptr != nextm->b_wptr);
290 
291 			m = nextm;	/* for x_base */
292 		}
293 	}
294 	xdrs->x_base = (caddr_t)m;
295 	xdrs->x_handy = m != NULL ? MBLKL(m) : 0;
296 	return (TRUE);
297 }
298 
299 static bool_t
300 xdrmblk_putbytes(XDR *xdrs, caddr_t addr, int len)
301 {
302 	mblk_t *m;
303 	uint_t i;
304 
305 	/* LINTED pointer alignment */
306 	m = (mblk_t *)xdrs->x_base;
307 	if (m == NULL)
308 		return (FALSE);
309 	/*
310 	 * Performance tweak: converted explicit bcopy()
311 	 * call to simple in-line. This function is called
312 	 * to process things like readdir reply filenames
313 	 * which are small strings--typically 12 bytes or less.
314 	 * Overhead of calling bcopy() is obnoxious for such
315 	 * small copies.
316 	 */
317 	while ((xdrs->x_handy -= len) < 0) {
318 		if ((xdrs->x_handy += len) > 0) {
319 			if (xdrs->x_handy < XDRMBLK_BCOPY_LIMIT) {
320 				for (i = 0; i < (uint_t)xdrs->x_handy; i++)
321 					*m->b_wptr++ = *addr++;
322 			} else {
323 				bcopy(addr, m->b_wptr, xdrs->x_handy);
324 				m->b_wptr += xdrs->x_handy;
325 				addr += xdrs->x_handy;
326 			}
327 			len -= xdrs->x_handy;
328 		}
329 
330 		/*
331 		 * We don't have enough space, so allocate the
332 		 * amount we need, or x_private, whichever is larger.
333 		 * It is better to let the underlying transport divide
334 		 * large chunks than to try and guess what is best.
335 		 */
336 		if (m->b_cont == NULL)
337 			m->b_cont = xdrmblk_alloc(MAX(len,
338 			    (int)(uintptr_t)xdrs->x_private));
339 
340 		m = m->b_cont;
341 		xdrs->x_base = (caddr_t)m;
342 		if (m == NULL) {
343 			xdrs->x_handy = 0;
344 			return (FALSE);
345 		}
346 		xdrs->x_handy = (int)(m->b_datap->db_lim - m->b_rptr);
347 		ASSERT(m->b_rptr == m->b_wptr);
348 		ASSERT(m->b_rptr >= m->b_datap->db_base);
349 		ASSERT(m->b_rptr < m->b_datap->db_lim);
350 	}
351 	if (len < XDRMBLK_BCOPY_LIMIT) {
352 		for (i = 0; i < len; i++)
353 			*m->b_wptr++ = *addr++;
354 	} else {
355 		bcopy(addr, m->b_wptr, len);
356 		m->b_wptr += len;
357 	}
358 	ASSERT(m->b_wptr <= m->b_datap->db_lim);
359 	return (TRUE);
360 }
361 
362 /*
363  * We avoid a copy by merely adding this mblk to the list.  The caller is
364  * responsible for allocating and filling in the mblk. If len is
365  * not a multiple of BYTES_PER_XDR_UNIT, the caller has the option
366  * of making the data a BYTES_PER_XDR_UNIT multiple (b_wptr - b_rptr is
367  * a BYTES_PER_XDR_UNIT multiple), but in this case the caller has to ensure
368  * that the filler bytes are initialized to zero. Note: Doesn't to work for
369  * chained mblks.
370  */
371 bool_t
372 xdrmblk_putmblk(XDR *xdrs, mblk_t *m, uint_t len)
373 {
374 	int32_t llen = (int32_t)len;
375 
376 	if (((m->b_wptr - m->b_rptr) % BYTES_PER_XDR_UNIT) != 0)
377 		return (FALSE);
378 	if (!xdrmblk_putint32(xdrs, &llen))
379 		return (FALSE);
380 	/* LINTED pointer alignment */
381 	((mblk_t *)xdrs->x_base)->b_cont = m;
382 	xdrs->x_base = (caddr_t)m;
383 	xdrs->x_handy = 0;
384 	return (TRUE);
385 }
386 
387 static uint_t
388 xdrmblk_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 
404 static bool_t
405 xdrmblk_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
434 static int xdrmblk_inline_hits = 0;
435 static int xdrmblk_inline_misses = 0;
436 static int do_xdrmblk_inline = 1;
437 #endif
438 
439 static rpc_inline_t *
440 xdrmblk_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 
494 static bool_t
495 xdrmblk_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 
557 static mblk_t *
558 xdrmblk_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