xref: /titanic_50/usr/src/lib/libc/port/stdio/flush.c (revision 554ff184129088135ad2643c1c9832174a17be88)
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 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*	Copyright (c) 1988 AT&T	*/
30 /*	  All Rights Reserved  	*/
31 
32 
33 #include "synonyms.h"
34 #include "mtlib.h"
35 #include "file64.h"
36 
37 #define	_iob	__iob
38 
39 #include <sys/types.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <thread.h>
43 #include <synch.h>
44 #include <unistd.h>
45 #include <string.h>
46 #include "stdiom.h"
47 #include <wchar.h>
48 #include <sys/stat.h>
49 #include <stddef.h>
50 #include <errno.h>
51 
52 #undef end
53 
54 #define	FILE_ARY_SZ	8 /* a nice size for FILE array & end_buffer_ptrs */
55 
56 #ifdef	_LP64
57 
58 /*
59  * Macros to declare and loop over a fp or fp/xfp combo to
60  * avoid some of the _LP64 ifdef hell.
61  */
62 
63 #define	FPDECL(fp)		FILE *fp
64 #define	FIRSTFP(lp, fp)		fp = lp->iobp
65 #define	NEXTFP(fp)		fp++
66 
67 #define	xFILE			FILE
68 
69 #else
70 
71 #define	FPDECL(fp)		FILE *fp; xFILE *x##fp
72 #define	FIRSTFP(lp, fp)		x##fp = lp->iobp; \
73 				fp = x##fp ? &x##fp->_iob : &_iob[0]
74 #define	NEXTFP(fp)		(x##fp ? fp = &(++x##fp)->_iob : ++fp)
75 
76 /* The extended 32-bit file structure for use in link buffers */
77 typedef struct xFILE {
78 	FILE			_iob;		/* must be first! */
79 	struct xFILEdata	_xdat;
80 } xFILE;
81 
82 #define	xmagic			_xdat._magic
83 #define	xend			_xdat._end
84 #define	xlock			_xdat._lock
85 #define	xstate			_xdat._state
86 
87 #define	FILEx(fp)		((struct xFILE *)(uintptr_t)fp)
88 
89 /*
90  * The magic number stored is actually the pointer scrambled with
91  * a magic number.  Pointers to data items live everywhere in memory
92  * so we scramble the pointer in order to avoid accidental collisions.
93  */
94 #define	XFILEMAGIC		0x63687367
95 #define	XMAGIC(xfp)		((uintptr_t)(xfp) ^ XFILEMAGIC)
96 
97 #endif /* _LP64 */
98 
99 struct _link_	/* manages a list of streams */
100 {
101 	xFILE *iobp;		/* the array of (x)FILE's */
102 				/* NULL for the __first_link in ILP32 */
103 	int	niob;		/* length of the arrays */
104 	struct _link_	*next;	/* next in the list */
105 };
106 
107 /*
108  * With dynamic linking, iob may be in either the library or in the user's
109  * a.out, so the run time linker fixes up the first entry in __first_link at
110  * process startup time.
111  *
112  * In 32 bit processes, we don't have xFILE[FILE_ARY_SZ] but FILE[],
113  * and _xftab[] instead; this is denoted by having iobp set to NULL in
114  * 32 bit mode for the first link entry.
115  */
116 struct _link_ __first_link =	/* first in linked list */
117 {
118 #if !defined(_LP64)
119 	NULL,
120 #else
121 	&_iob[0],
122 #endif
123 	_NFILE,
124 	NULL
125 };
126 
127 /*
128  * Information cached to speed up searches.  We remember where we
129  * last found a free FILE* and we remember whether we saw any fcloses
130  * in between.  We also count the number of chunks we allocated, see
131  * _findiop() for an explanation.
132  * These variables are all protected by _first_link_lock.
133  */
134 static struct _link_ *lastlink = NULL;
135 static int fcloses;
136 static int nchunks;
137 
138 static rwlock_t _first_link_lock = DEFAULTRWLOCK;
139 
140 static int _fflush_u_iops(void);
141 static FILE *getiop(FILE *, rmutex_t *, mbstate_t *);
142 
143 #define	GETIOP(fp, lk, mb)	{FILE *ret; \
144 	if ((ret = getiop((fp), __threaded ? (lk) : NULL, (mb))) != NULL) { \
145 		if (__threaded) \
146 			(void) __rw_unlock(&_first_link_lock); \
147 		return (ret); \
148 	}; \
149 	}
150 
151 /*
152  * All functions that understand the linked list of iob's follow.
153  */
154 #pragma weak _cleanup = __cleanup
155 void
156 __cleanup(void)		/* called at process end to flush ouput streams */
157 {
158 	(void) fflush(NULL);
159 }
160 
161 /*
162  * For fork1-safety (see libc_prepare_atfork(), etc).
163  */
164 void
165 stdio_locks()
166 {
167 	(void) __rw_wrlock(&_first_link_lock);
168 	/*
169 	 * XXX: We should acquire all of the iob locks here.
170 	 */
171 }
172 
173 void
174 stdio_unlocks()
175 {
176 	/*
177 	 * XXX: We should release all of the iob locks here.
178 	 */
179 	(void) __rw_unlock(&_first_link_lock);
180 }
181 
182 void
183 _flushlbf(void)		/* fflush() all line-buffered streams */
184 {
185 	FPDECL(fp);
186 	int i;
187 	struct _link_ *lp;
188 
189 	if (__threaded)
190 		(void) __rw_rdlock(&_first_link_lock);
191 
192 	lp = &__first_link;
193 	do {
194 		FIRSTFP(lp, fp);
195 		for (i = lp->niob; --i >= 0; NEXTFP(fp)) {
196 			if ((fp->_flag & (_IOLBF | _IOWRT)) ==
197 			    (_IOLBF | _IOWRT))
198 				(void) _fflush_u(fp);
199 		}
200 	} while ((lp = lp->next) != NULL);
201 
202 	if (__threaded)
203 		(void) __rw_unlock(&_first_link_lock);
204 }
205 
206 /* allocate an unused stream; NULL if cannot */
207 FILE *
208 _findiop(void)
209 {
210 	struct _link_ *lp, **prev;
211 
212 	/* used so there only needs to be one malloc() */
213 #ifdef _LP64
214 	typedef	struct	{
215 		struct _link_	hdr;
216 		FILE	iob[FILE_ARY_SZ];
217 	} Pkg;
218 #else
219 	typedef union {
220 		struct {				/* Normal */
221 			struct _link_	hdr;
222 			xFILE	iob[FILE_ARY_SZ];
223 		} Pkgn;
224 		struct {				/* Reversed */
225 			xFILE	iob[FILE_ARY_SZ];
226 			struct _link_	hdr;
227 		} Pkgr;
228 	} Pkg;
229 	uintptr_t delta;
230 #endif
231 	Pkg *pkgp;
232 	struct _link_ *hdr;
233 	FPDECL(fp);
234 	int i;
235 
236 	if (__threaded)
237 		(void) __rw_wrlock(&_first_link_lock);
238 
239 	if (lastlink == NULL) {
240 rescan:
241 		fcloses = 0;
242 		lastlink = &__first_link;
243 	}
244 
245 	lp = lastlink;
246 
247 	/*
248 	 * lock to make testing of fp->_flag == 0 and acquiring the fp atomic
249 	 * and for allocation of new links
250 	 * low contention expected on _findiop(), hence coarse locking.
251 	 * for finer granularity, use fp->_lock for allocating an iop
252 	 * and make the testing of lp->next and allocation of new link atomic
253 	 * using lp->_lock
254 	 */
255 
256 	do {
257 		prev = &lp->next;
258 		FIRSTFP(lp, fp);
259 
260 		for (i = lp->niob; --i >= 0; NEXTFP(fp)) {
261 #ifdef	_LP64
262 			GETIOP(fp, &fp->_lock, &fp->_state);
263 #else
264 			GETIOP(fp,
265 			    xfp ? &xfp->xlock : &_xftab[IOPIND(fp)]._lock,
266 			    xfp ? &xfp->xstate : &_xftab[IOPIND(fp)]._state);
267 #endif	/*	_LP64	*/
268 		}
269 	} while ((lastlink = lp = lp->next) != NULL);
270 
271 	/*
272 	 * If there was a sufficient number of  fcloses since we last started
273 	 * at __first_link, we rescan all fp's again.  We do not rescan for
274 	 * all fcloses; that would simplify the algorithm but would make
275 	 * search times near O(n) again.
276 	 * Worst case behaviour would still be pretty bad (open a full set,
277 	 * then continously opening and closing one FILE * gets you a full
278 	 * scan each time).  That's why we over allocate 1 FILE for each
279 	 * 32 chunks.  More over allocation is better; this is a nice
280 	 * empirical value which doesn't cost a lot of memory, doesn't
281 	 * overallocate until we reach 256 FILE *s and keeps the performance
282 	 * pretty close to the optimum.
283 	 */
284 	if (fcloses > nchunks/32)
285 		goto rescan;
286 
287 	/*
288 	 * Need to allocate another and put it in the linked list.
289 	 */
290 	if ((pkgp = malloc(sizeof (Pkg))) == NULL) {
291 		if (__threaded)
292 			(void) __rw_unlock(&_first_link_lock);
293 		return (NULL);
294 	}
295 
296 	(void) memset(pkgp, 0, sizeof (Pkg));
297 
298 #ifdef _LP64
299 	hdr = &pkgp->hdr;
300 	hdr->iobp = &pkgp->iob[0];
301 #else
302 	/*
303 	 * The problem with referencing a word after a FILE* is the possibility
304 	 * of a SIGSEGV if a non-stdio issue FILE structure ends on a page
305 	 * boundary.  We run this check so we never need to run an expensive
306 	 * check like mincore() in order to know whether it is
307 	 * safe to dereference ((xFILE*)fp)->xmagic.
308 	 * We allocate the block with two alternative layouts; if one
309 	 * layout is not properly aligned for our purposes, the other layout
310 	 * will be because the size of _link_ is small compared to
311 	 * sizeof (xFILE).
312 	 * The check performed is this:
313 	 *	If the distance from pkgp to the end of the page is
314 	 *	less than the the offset of the last xmagic field in the
315 	 *	xFILE structure, (the 0x1000 boundary is inside our just
316 	 *	allocated structure) and the distance modulo the size of xFILE
317 	 *	is identical to the offset of the first xmagic in the
318 	 *	structure (i.e., XXXXXX000 points to an xmagic field),
319 	 *	we need to use the reverse structure.
320 	 */
321 	if ((delta = 0x1000 - ((uintptr_t)pkgp & 0xfff)) <=
322 				offsetof(Pkg, Pkgn.iob[FILE_ARY_SZ-1].xmagic) &&
323 	    delta % sizeof (struct xFILE) ==
324 		    offsetof(Pkg, Pkgn.iob[0].xmagic)) {
325 		/* Use reversed structure */
326 		hdr = &pkgp->Pkgr.hdr;
327 		hdr->iobp = &pkgp->Pkgr.iob[0];
328 	} else {
329 		/* Use normal structure */
330 		hdr = &pkgp->Pkgn.hdr;
331 		hdr->iobp = &pkgp->Pkgn.iob[0];
332 	}
333 #endif /* _LP64 */
334 
335 	hdr->niob = FILE_ARY_SZ;
336 	nchunks++;
337 
338 #ifdef	_LP64
339 	fp = hdr->iobp;
340 	for (i = 0; i < FILE_ARY_SZ; i++)
341 		_private_mutex_init(&fp[i]._lock,
342 			USYNC_THREAD|LOCK_RECURSIVE, NULL);
343 #else
344 	xfp = hdr->iobp;
345 	fp = &xfp->_iob;
346 
347 	for (i = 0; i < FILE_ARY_SZ; i++) {
348 		xfp[i].xmagic = XMAGIC(&xfp[i]);
349 		_private_mutex_init(&xfp[i].xlock,
350 			USYNC_THREAD|LOCK_RECURSIVE, NULL);
351 	}
352 #endif	/*	_LP64	*/
353 
354 	lastlink = *prev = hdr;
355 	fp->_ptr = 0;
356 	fp->_base = 0;
357 	fp->_flag = 0377; /* claim the fp by setting low 8 bits */
358 	if (__threaded)
359 		(void) __rw_unlock(&_first_link_lock);
360 
361 	return (fp);
362 }
363 
364 static void
365 isseekable(FILE *iop)
366 {
367 	struct stat64 fstatbuf;
368 	int save_errno;
369 
370 	save_errno = errno;
371 
372 	if (fstat64(iop->_file, &fstatbuf) != 0) {
373 		/*
374 		 * when we don't know what it is we'll
375 		 * do the old behaviour and flush
376 		 * the stream
377 		 */
378 		SET_SEEKABLE(iop);
379 		errno = save_errno;
380 		return;
381 	}
382 
383 	/*
384 	 * check for what is non-SEEKABLE
385 	 * otherwise assume it's SEEKABLE so we get the old
386 	 * behaviour and flush the stream
387 	 */
388 
389 	if (S_ISFIFO(fstatbuf.st_mode) || S_ISCHR(fstatbuf.st_mode) ||
390 	    S_ISSOCK(fstatbuf.st_mode) || S_ISDOOR(fstatbuf.st_mode)) {
391 		CLEAR_SEEKABLE(iop);
392 	} else {
393 		SET_SEEKABLE(iop);
394 	}
395 
396 	errno = save_errno;
397 }
398 
399 #ifdef	_LP64
400 void
401 _setbufend(FILE *iop, Uchar *end)	/* set the end pointer for this iop */
402 {
403 	iop->_end = end;
404 
405 	isseekable(iop);
406 }
407 
408 #undef _realbufend
409 
410 Uchar *
411 _realbufend(FILE *iop)		/* get the end pointer for this iop */
412 {
413 	return (iop->_end);
414 }
415 
416 #else /* _LP64 */
417 
418 /*
419  * Awkward functions not needed for the sane 64 bit environment.
420  */
421 /*
422  * xmagic must not be aligned on a 4K boundary. We guarantee this in
423  * _findiop().
424  */
425 #define	VALIDXFILE(xfp) \
426 	(((uintptr_t)&(xfp)->xmagic & 0xfff) && \
427 	    (xfp)->xmagic == XMAGIC(FILEx(xfp)))
428 
429 static struct xFILEdata *
430 getxfdat(FILE *iop)
431 {
432 	if (STDIOP(iop))
433 		return (&_xftab[IOPIND(iop)]);
434 	else if (VALIDXFILE(FILEx(iop)))
435 		return (&FILEx(iop)->_xdat);
436 	else
437 		return (NULL);
438 }
439 
440 void
441 _setbufend(FILE *iop, Uchar *end)	/* set the end pointer for this iop */
442 {
443 	struct xFILEdata *dat = getxfdat(iop);
444 
445 	if (dat != NULL)
446 		dat->_end = end;
447 
448 	isseekable(iop);
449 
450 	/*
451 	 * For binary compatibility with user programs using the
452 	 * old _bufend macro.  This is *so* broken, fileno()
453 	 * is not the proper index.
454 	 */
455 	if (iop->_file < _NFILE)
456 		_bufendtab[iop->_file] = end;
457 
458 }
459 
460 Uchar *
461 _realbufend(FILE *iop)		/* get the end pointer for this iop */
462 {
463 	struct xFILEdata *dat = getxfdat(iop);
464 
465 	if (dat != NULL)
466 		return (dat->_end);
467 
468 	return (NULL);
469 }
470 
471 /*
472  * _reallock() is invoked in each stdio call through the IOB_LCK() macro,
473  * it is therefor extremely performance sensitive.  We get better performance
474  * by inlining the STDIOP check in IOB_LCK and inlining a custom version
475  * of getfxdat() here.
476  */
477 rmutex_t *
478 _reallock(FILE *iop)
479 {
480 	if (VALIDXFILE(FILEx(iop)))
481 		return (&FILEx(iop)->xlock);
482 
483 	return (NULL);
484 }
485 
486 #endif	/*	_LP64	*/
487 
488 /* make sure _cnt, _ptr are correct */
489 void
490 _bufsync(FILE *iop, Uchar *bufend)
491 {
492 	ssize_t spaceleft;
493 
494 	spaceleft = bufend - iop->_ptr;
495 	if (bufend < iop->_ptr) {
496 		iop->_ptr = bufend;
497 		iop->_cnt = 0;
498 	} else if (spaceleft < iop->_cnt)
499 		iop->_cnt = spaceleft;
500 }
501 
502 /* really write out current buffer contents */
503 int
504 _xflsbuf(FILE *iop)
505 {
506 	ssize_t n;
507 	Uchar *base = iop->_base;
508 	Uchar *bufend;
509 	ssize_t num_wrote;
510 
511 	/*
512 	 * Hopefully, be stable with respect to interrupts...
513 	 */
514 	n = iop->_ptr - base;
515 	iop->_ptr = base;
516 	bufend = _bufend(iop);
517 	if (iop->_flag & (_IOLBF | _IONBF))
518 		iop->_cnt = 0;		/* always go to a flush */
519 	else
520 		iop->_cnt = bufend - base;
521 
522 	if (_needsync(iop, bufend))	/* recover from interrupts */
523 		_bufsync(iop, bufend);
524 
525 	if (n > 0) {
526 		while ((num_wrote =
527 			write(iop->_file, base, (size_t)n)) != n) {
528 			if (num_wrote <= 0) {
529 				iop->_flag |= _IOERR;
530 				return (EOF);
531 			}
532 			n -= num_wrote;
533 			base += num_wrote;
534 		}
535 	}
536 	return (0);
537 }
538 
539 /* flush (write) buffer */
540 int
541 fflush(FILE *iop)
542 {
543 	int res;
544 	rmutex_t *lk;
545 
546 	if (iop) {
547 		FLOCKFILE(lk, iop);
548 		res = _fflush_u(iop);
549 		FUNLOCKFILE(lk);
550 	} else {
551 		res = _fflush_u_iops();		/* flush all iops */
552 	}
553 	return (res);
554 }
555 
556 static int
557 _fflush_u_iops(void)		/* flush all buffers */
558 {
559 	FPDECL(iop);
560 
561 	int i;
562 	struct _link_ *lp;
563 	int res = 0;
564 
565 	if (__threaded)
566 		(void) __rw_rdlock(&_first_link_lock);
567 
568 	lp = &__first_link;
569 
570 	do {
571 		/*
572 		 * Don't grab the locks for these file pointers
573 		 * since they are supposed to be flushed anyway
574 		 * It could also be the case in which the 2nd
575 		 * portion (base and lock) are not initialized
576 		 */
577 		FIRSTFP(lp, iop);
578 		for (i = lp->niob; --i >= 0; NEXTFP(iop)) {
579 		    if (!(iop->_flag & _IONBF)) {
580 			/*
581 			 * don't need to worry about the _IORW case
582 			 * since the iop will also marked with _IOREAD
583 			 * or _IOWRT whichever we are really doing
584 			 */
585 			if (iop->_flag & _IOWRT) {    /* flush write buffers */
586 			    res |= _fflush_u(iop);
587 			} else if (iop->_flag & _IOREAD) {
588 				/*
589 				 * flush seekable read buffers
590 				 * don't flush non-seekable read buffers
591 				 */
592 			    if (GET_SEEKABLE(iop)) {
593 				res |= _fflush_u(iop);
594 			    }
595 			}
596 		    }
597 		}
598 	} while ((lp = lp->next) != NULL);
599 	if (__threaded)
600 		(void) __rw_unlock(&_first_link_lock);
601 	return (res);
602 }
603 
604 /* flush buffer */
605 int
606 _fflush_u(FILE *iop)
607 {
608 	int res = 0;
609 
610 	/* this portion is always assumed locked */
611 	if (!(iop->_flag & _IOWRT)) {
612 		(void) lseek64(iop->_file, -iop->_cnt, SEEK_CUR);
613 		iop->_cnt = 0;
614 		/* needed for ungetc & multibyte pushbacks */
615 		iop->_ptr = iop->_base;
616 		if (iop->_flag & _IORW) {
617 			iop->_flag &= ~_IOREAD;
618 		}
619 		return (0);
620 	}
621 	if (iop->_base != NULL && iop->_ptr > iop->_base) {
622 		res = _xflsbuf(iop);
623 	}
624 	if (iop->_flag & _IORW) {
625 		iop->_flag &= ~_IOWRT;
626 		iop->_cnt = 0;
627 	}
628 	return (res);
629 }
630 
631 /* flush buffer and close stream */
632 int
633 fclose(FILE *iop)
634 {
635 	int res = 0;
636 	rmutex_t *lk;
637 
638 	if (iop == NULL) {
639 		return (EOF);		/* avoid passing zero to FLOCKFILE */
640 	}
641 
642 	FLOCKFILE(lk, iop);
643 	if (iop->_flag == 0) {
644 		FUNLOCKFILE(lk);
645 		return (EOF);
646 	}
647 	/* Is not unbuffered and opened for read and/or write ? */
648 	if (!(iop->_flag & _IONBF) && (iop->_flag & (_IOWRT | _IOREAD | _IORW)))
649 		res = _fflush_u(iop);
650 	if (close(iop->_file) < 0)
651 		res = EOF;
652 	if (iop->_flag & _IOMYBUF) {
653 		(void) free((char *)iop->_base - PUSHBACK);
654 	}
655 	iop->_base = NULL;
656 	iop->_ptr = NULL;
657 	iop->_cnt = 0;
658 	iop->_flag = 0;			/* marks it as available */
659 	FUNLOCKFILE(lk);
660 
661 	if (__threaded)
662 		(void) __rw_wrlock(&_first_link_lock);
663 	fcloses++;
664 	if (__threaded)
665 		(void) __rw_unlock(&_first_link_lock);
666 
667 	return (res);
668 }
669 
670 /* flush buffer, close fd but keep the stream used by freopen() */
671 int
672 close_fd(FILE *iop)
673 {
674 	int res = 0;
675 	mbstate_t *mb;
676 
677 	if (iop == NULL || iop->_flag == 0)
678 		return (EOF);
679 	/* Is not unbuffered and opened for read and/or write ? */
680 	if (!(iop->_flag & _IONBF) && (iop->_flag & (_IOWRT | _IOREAD | _IORW)))
681 		res = _fflush_u(iop);
682 	if (close(iop->_file) < 0)
683 		res = EOF;
684 	if (iop->_flag & _IOMYBUF) {
685 		(void) free((char *)iop->_base - PUSHBACK);
686 	}
687 	iop->_base = NULL;
688 	iop->_ptr = NULL;
689 	mb = _getmbstate(iop);
690 	if (mb != NULL)
691 		(void) memset(mb, 0, sizeof (mbstate_t));
692 	iop->_cnt = 0;
693 	_setorientation(iop, _NO_MODE);
694 	return (res);
695 }
696 
697 static FILE *
698 getiop(FILE *fp, rmutex_t *lk, mbstate_t *mb)
699 {
700 	if (lk != NULL && rmutex_trylock(lk))
701 		return (NULL);	/* locked: fp in use */
702 
703 	if (fp->_flag == 0) {	/* unused */
704 #ifndef	_LP64
705 		fp->__orientation = 0;
706 #endif /* _LP64 */
707 		fp->_cnt = 0;
708 		fp->_ptr = NULL;
709 		fp->_base = NULL;
710 		fp->_flag = 0377;	/* claim the fp by setting low 8 bits */
711 		(void) memset(mb, 0, sizeof (mbstate_t));
712 		FUNLOCKFILE(lk);
713 		return (fp);
714 	}
715 	FUNLOCKFILE(lk);
716 	return (NULL);
717 }
718 
719 #ifndef	_LP64
720 /*
721  * DESCRIPTION:
722  * This function gets the pointer to the mbstate_t structure associated
723  * with the specified iop.
724  *
725  * RETURNS:
726  * If the associated mbstate_t found, the pointer to the mbstate_t is
727  * returned.  Otherwise, NULL is returned.
728  */
729 mbstate_t *
730 _getmbstate(FILE *iop)
731 {
732 	struct xFILEdata *dat = getxfdat(iop);
733 
734 	if (dat != NULL)
735 		return (&dat->_state);
736 
737 	return (NULL);
738 }
739 #endif
740