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