xref: /netbsd-src/share/man/man9/bufferio.9 (revision 88fd7a1b6c2674ac2473a701e55ac2e48a544ca1)
1*88fd7a1bSsevan.\"	$NetBSD: bufferio.9,v 1.18 2019/09/12 21:08:35 sevan Exp $
21098217eSriastradh.\"
31098217eSriastradh.\" Copyright (c) 2015 The NetBSD Foundation, Inc.
41098217eSriastradh.\" All rights reserved.
51098217eSriastradh.\"
61098217eSriastradh.\" This code is derived from software contributed to The NetBSD Foundation
71098217eSriastradh.\" by Taylor R. Campbell.
81098217eSriastradh.\"
91098217eSriastradh.\" Redistribution and use in source and binary forms, with or without
101098217eSriastradh.\" modification, are permitted provided that the following conditions
111098217eSriastradh.\" are met:
121098217eSriastradh.\" 1. Redistributions of source code must retain the above copyright
131098217eSriastradh.\"    notice, this list of conditions and the following disclaimer.
141098217eSriastradh.\" 2. Redistributions in binary form must reproduce the above copyright
151098217eSriastradh.\"    notice, this list of conditions and the following disclaimer in the
161098217eSriastradh.\"    documentation and/or other materials provided with the distribution.
171098217eSriastradh.\"
181098217eSriastradh.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
191098217eSriastradh.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
201098217eSriastradh.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
211098217eSriastradh.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
221098217eSriastradh.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
231098217eSriastradh.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
241098217eSriastradh.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
251098217eSriastradh.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
261098217eSriastradh.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
271098217eSriastradh.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
281098217eSriastradh.\" POSSIBILITY OF SUCH DAMAGE.
291098217eSriastradh.\"
30*88fd7a1bSsevan.Dd September 12, 2019
311098217eSriastradh.Dt BUFFERIO 9
321098217eSriastradh.Os
331098217eSriastradh.Sh NAME
341098217eSriastradh.Nm BUFFERIO ,
351098217eSriastradh.Nm biodone ,
361098217eSriastradh.Nm biowait ,
371098217eSriastradh.Nm getiobuf ,
381098217eSriastradh.Nm putiobuf ,
391098217eSriastradh.Nm nestiobuf_setup ,
401098217eSriastradh.Nm nestiobuf_done
411098217eSriastradh.Nd block I/O buffer transfers
421098217eSriastradh.Sh SYNOPSIS
431098217eSriastradh.In sys/buf.h
441098217eSriastradh.Ft void
45*88fd7a1bSsevan.Fn biodone "buf_t *bp"
461098217eSriastradh.Ft int
47*88fd7a1bSsevan.Fn biowait "buf_t *bp"
48*88fd7a1bSsevan.Ft buf_t *
491098217eSriastradh.Fn getiobuf "struct vnode *vp" "bool waitok"
501098217eSriastradh.Ft void
51*88fd7a1bSsevan.Fn putiobuf "buf_t *bp"
521098217eSriastradh.Ft void
53*88fd7a1bSsevan.Fn nestiobuf_setup "buf_t *mbp" "buf_t *bp" "int offset" \
541098217eSriastradh        "size_t size"
551098217eSriastradh.Ft void
56*88fd7a1bSsevan.Fn nestiobuf_done "buf_t *mbp" "int donebytes" "int error"
571098217eSriastradh.Sh DESCRIPTION
581098217eSriastradhThe
591098217eSriastradh.Nm
601098217eSriastradhsubsystem manages block I/O buffer transfers, described by the
611098217eSriastradh.Vt "struct buf"
621098217eSriastradhstructure, which serves multiple purposes between users in
631098217eSriastradh.Nm ,
641098217eSriastradhusers in
651098217eSriastradh.Xr buffercache 9 ,
661098217eSriastradhand users in block device drivers to execute transfers to physical
671098217eSriastradhdisks.
681098217eSriastradh.Sh BLOCK DEVICE USERS
691098217eSriastradhUsers of
701098217eSriastradh.Nm
711098217eSriastradhwishing to submit a buffer for block I/O transfer must obtain a
721098217eSriastradh.Vt "struct buf" ,
731098217eSriastradhe.g. via
741098217eSriastradh.Fn getiobuf ,
751098217eSriastradhfill its parameters, and submit it to a block device with
761098217eSriastradh.Xr bdev_strategy 9 ,
771098217eSriastradhusually via
781098217eSriastradh.Xr VOP_STRATEGY 9 .
791098217eSriastradh.Pp
801098217eSriastradhThe parameters to an I/O transfer described by
811098217eSriastradh.Fa bp
821098217eSriastradhare specified by the following
831098217eSriastradh.Vt "struct buf"
841098217eSriastradhfields:
85*88fd7a1bSsevan.Bl -tag -width 6n -offset abcd
861098217eSriastradh.It Fa bp Ns Li "->b_flags"
871098217eSriastradhFlags specifying the type of transfer.
88*88fd7a1bSsevan.Bl -tag -width 6n -compact
891098217eSriastradh.It Dv B_READ
901098217eSriastradhTransfer is read from device.
911098217eSriastradhIf not set, transfer is write to device.
921098217eSriastradh.It Dv B_ASYNC
931098217eSriastradhAsynchronous I/O.
94ba2aeff9SriastradhCaller must not provide
951098217eSriastradh.Fa bp Ns Li "->b_iodone"
961098217eSriastradhand must not call
971098217eSriastradh.Fn biowait bp .
981098217eSriastradh.El
997643fefeSriastradhFor legibility, callers should indicate writes by passing the
1007643fefeSriastradhpseudo-flag
1017643fefeSriastradh.Dv B_WRITE ,
1025bd51b95Sriastradhwhich is zero.
1031098217eSriastradh.It Fa bp Ns Li "->b_data"
1041098217eSriastradhPointer to kernel virtual address of source/target for transfer.
1051098217eSriastradh.It Fa bp Ns Li "->b_bcount"
1061098217eSriastradhNonnegative number of bytes requested for transfer.
1071098217eSriastradh.It Fa bp Ns Li "->b_blkno"
1081098217eSriastradhBlock number at which to do transfer.
1091098217eSriastradh.It Fa bp Ns Li "->b_iodone"
110ba2aeff9SriastradhI/O completion callback.
1111098217eSriastradh.Dv B_ASYNC
112ba2aeff9Sriastradhmust not be set in
113ba2aeff9Sriastradh.Fa bp Ns Li "->b_flags" .
1141098217eSriastradh.El
1151098217eSriastradh.Pp
1161098217eSriastradhAdditionally, if the I/O transfer is a write associated with a
1171098217eSriastradh.Xr vnode 9
1181098217eSriastradh.Fa vp ,
1191098217eSriastradhthen before the user submits it to a block device, the user must
1201098217eSriastradhincrement
1211098217eSriastradh.Fa vp Ns Li "->v_numoutput" .
1221098217eSriastradhThe user must not acquire
1237cfc1b80Sriastradh.Fa vp Ns Ap s
1241098217eSriastradhvnode lock between incrementing
1251098217eSriastradh.Fa vp Ns Li "->v_numoutput"
1261098217eSriastradhand submitting
1271098217eSriastradh.Fa bp
12888f86411Sriastradhto a block device \(em doing so will likely cause deadlock with the
1291098217eSriastradhsyncer.
1301098217eSriastradh.Pp
131ba2aeff9SriastradhBlock I/O transfer completion may be notified by the
132ba2aeff9Sriastradh.Fa bp Ns Li "->b_iodone"
133ba2aeff9Sriastradhcallback, by signalling
134ba2aeff9Sriastradh.Fn biowait
135ba2aeff9Sriastradhwaiters, or not at all in the
136ba2aeff9Sriastradh.Dv B_ASYNC
137ba2aeff9Sriastradhcase.
1381098217eSriastradh.Bl -dash
1391098217eSriastradh.It
140ba2aeff9SriastradhIf the user sets the
1411098217eSriastradh.Fa bp Ns Li "->b_iodone"
142078e20efSriastradhcallback to a
143078e20efSriastradh.Pf non- Dv NULL
144078e20efSriastradhfunction pointer, it will be called in soft interrupt context when the
145078e20efSriastradhI/O transfer is complete.
1461098217eSriastradhThe user
1471098217eSriastradh.Em may not
1481098217eSriastradhcall
1491098217eSriastradh.Fn biowait bp
1501098217eSriastradhin this case.
151ba2aeff9Sriastradh.It
152ba2aeff9SriastradhIf
153ba2aeff9Sriastradh.Dv B_ASYNC
154ba2aeff9Sriastradhis set, then the I/O transfer is asynchronous and the user will not be
155ba2aeff9Sriastradhnotified when it is completed.
156ba2aeff9SriastradhThe user
157ba2aeff9Sriastradh.Em may not
158ba2aeff9Sriastradhcall
159ba2aeff9Sriastradh.Fn biowait bp
160ba2aeff9Sriastradhin this case.
161ba2aeff9Sriastradh.It
162ba2aeff9SriastradhOtherwise, if
163ba2aeff9Sriastradh.Fa bp Ns Li "->b_iodone"
164078e20efSriastradhis
165078e20efSriastradh.Dv NULL
166078e20efSriastradhand
167ba2aeff9Sriastradh.Dv B_ASYNC
168ba2aeff9Sriastradhis not specified, the user may wait for the I/O transfer to complete
169ba2aeff9Sriastradhwith
170ba2aeff9Sriastradh.Fn biowait bp .
1711098217eSriastradh.El
172e90024bfSriastradh.Pp
173e90024bfSriastradhOnce an I/O transfer has completed, its
174b2d5d8d7Sriastradh.Vt "struct buf"
175e90024bfSriastradhmay be reused, but the user must first clear the
176e90024bfSriastradh.Dv BO_DONE
177e90024bfSriastradhflag of
178e90024bfSriastradh.Fa bp Ns Li "->b_oflags"
179e90024bfSriastradhbefore reusing it.
1801098217eSriastradh.Sh NESTED I/O TRANSFERS
1811098217eSriastradhSometimes an I/O transfer from a single buffer in memory cannot go to a
1821098217eSriastradhsingle location on a block device: it must be split up into smaller
1831098217eSriastradhtransfers for each segment of the memory buffer.
1841098217eSriastradh.Pp
1851098217eSriastradhAfter initializing the
1861098217eSriastradh.Li b_flags ,
1871098217eSriastradh.Li b_data ,
1881098217eSriastradhand
1891098217eSriastradh.Li b_bcount
1901098217eSriastradhparameters of an I/O transfer for the buffer, called the
1911098217eSriastradh.Em master
1921098217eSriastradhbuffer, the user can issue smaller transfers for segments of the buffer
1931098217eSriastradhusing
1941098217eSriastradh.Fn nestiobuf_setup .
195808424fbSriastradhWhen nested I/O transfers complete, in any order, they debit from the
196808424fbSriastradhamount of work left to be done in the master buffer.
1971098217eSriastradhIf any segments of the buffer were skipped, the user can report this
1981098217eSriastradhwith
1991098217eSriastradh.Fn nestiobuf_done
2001098217eSriastradhto debit the skipped part of the work.
2011098217eSriastradh.Pp
2021098217eSriastradhThe master buffer's I/O transfer is completed when all nested buffers'
2031098217eSriastradhI/O transfers are completed, and if
2041098217eSriastradh.Fn nestiobuf_done
2051098217eSriastradhis called in the case of skipped segments.
2061098217eSriastradh.Pp
2071098217eSriastradhFor writes associated with a vnode
2081098217eSriastradh.Fa vp ,
2091098217eSriastradh.Fn nestiobuf_setup
2101098217eSriastradhaccounts for
2111098217eSriastradh.Fa vp Ns Li "->v_numoutput" ,
2121098217eSriastradhso the caller is not allowed to acquire
2137cfc1b80Sriastradh.Fa vp Ns Ap s
2141098217eSriastradhvnode lock before submitting the nested I/O transfer to a block
2151098217eSriastradhdevice.
2161098217eSriastradhHowever, the caller is responsible for accounting the master buffer in
2171098217eSriastradh.Fa vp Ns Li "->v_numoutput" .
2181098217eSriastradhThis must be done very carefully because after incrementing
2191098217eSriastradh.Fa vp Ns Li "->v_numoutput" ,
2201098217eSriastradhthe caller is not allowed to acquire
2217cfc1b80Sriastradh.Fa vp Ns Ap s
2221098217eSriastradhvnode lock before either calling
2231098217eSriastradh.Fn nestiobuf_done
2241098217eSriastradhor submitting the last nested I/O transfer to a block device.
2251098217eSriastradh.Pp
2261098217eSriastradhFor example:
2271098217eSriastradh.Bd -literal -offset abcd
2285bd51b95Sriastradhstruct buf *mbp, *bp;
2295bd51b95Sriastradhsize_t skipped = 0;
2305bd51b95Sriastradhunsigned i;
2315bd51b95Sriastradhint error = 0;
2325bd51b95Sriastradh
2335bd51b95Sriastradhmbp = getiobuf(vp, true);
2345bd51b95Sriastradhmbp->b_data = data;
2355bd51b95Sriastradhmbp->b_resid = mbp->b_bcount = datalen;
2365bd51b95Sriastradhmbp->b_flags = B_WRITE;
2375bd51b95Sriastradh
238185edd37SriastradhKASSERT(0 < nsegs);
239185edd37SriastradhKASSERT(datalen == nsegs*segsz);
2405bd51b95Sriastradhfor (i = 0; i < nsegs; i++) {
2415bd51b95Sriastradh	struct vnode *devvp;
2425bd51b95Sriastradh	daddr_t blkno;
2435bd51b95Sriastradh
2445bd51b95Sriastradh	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
2455bd51b95Sriastradh	error = VOP_BMAP(vp, i*segsz, &devvp, &blkno, NULL);
2465bd51b95Sriastradh	VOP_UNLOCK(vp);
24779047ecbSriastradh	if (error == 0 && blkno == -1)
2485bd51b95Sriastradh		error = EIO;
2495bd51b95Sriastradh	if (error) {
250185edd37Sriastradh		/* Give up early, don't try to handle holes.  */
251185edd37Sriastradh		skipped += datalen - i*segsz;
2525bd51b95Sriastradh		break;
2535bd51b95Sriastradh	}
2545bd51b95Sriastradh
2555bd51b95Sriastradh	bp = getiobuf(vp, true);
2565bd51b95Sriastradh	nestiobuf_setup(bp, mbp, i*segsz, segsz);
2575bd51b95Sriastradh	bp->b_blkno = blkno;
2585bd51b95Sriastradh	if (i == nsegs - 1)	/* Last segment.  */
2595bd51b95Sriastradh		break;
2605bd51b95Sriastradh	VOP_STRATEGY(devvp, bp);
2615bd51b95Sriastradh}
2625bd51b95Sriastradh
2635bd51b95Sriastradh/*
2645bd51b95Sriastradh * Account v_numoutput for master write.
2655bd51b95Sriastradh * (Must not vn_lock before last VOP_STRATEGY!)
2665bd51b95Sriastradh */
2675bd51b95Sriastradhmutex_enter(&vp->v_interlock);
2685bd51b95Sriastradhvp->v_numoutput++;
2695bd51b95Sriastradhmutex_exit(&vp->v_interlock);
2705bd51b95Sriastradh
2715bd51b95Sriastradhif (skipped)
2725bd51b95Sriastradh	nestiobuf_done(mbp, skipped, error);
2735bd51b95Sriastradhelse
2745bd51b95Sriastradh	VOP_STRATEGY(devvp, bp);
2751098217eSriastradh.Ed
2761098217eSriastradh.Sh BLOCK DEVICE DRIVERS
2771098217eSriastradhBlock device drivers implement a
2781098217eSriastradh.Sq strategy
2791098217eSriastradhmethod, in the
2801098217eSriastradh.Li d_strategy
2811098217eSriastradhmember of
2821098217eSriastradh.Li struct bdevsw
2831098217eSriastradh.Pq Xr driver 9 ,
2841098217eSriastradhto queue a buffer for disk I/O.
2851098217eSriastradhThe inputs to the strategy method are:
286*88fd7a1bSsevan.Bl -tag -width 6n -offset abcd
2871098217eSriastradh.It Fa bp Ns Li "->b_flags"
2881098217eSriastradhFlags specifying the type of transfer.
289*88fd7a1bSsevan.Bl -tag -width 6n -compact
2901098217eSriastradh.It Dv B_READ
2911098217eSriastradhTransfer is read from device.
2921098217eSriastradhIf not set, transfer is write to device.
2931098217eSriastradh.El
2941098217eSriastradh.It Fa bp Ns Li "->b_data"
2951098217eSriastradhPointer to kernel virtual address of source/target for transfer.
2961098217eSriastradh.It Fa bp Ns Li "->b_bcount"
2971098217eSriastradhNonnegative number of bytes requested for transfer.
2981098217eSriastradh.It Fa bp Ns Li "->b_blkno"
2991098217eSriastradhBlock number at which to do transfer, relative to partition start.
3001098217eSriastradh.El
3011098217eSriastradh.Pp
3021098217eSriastradhIf the strategy method uses
3031098217eSriastradh.Xr bufq 9 ,
3041098217eSriastradhit must additionally initialize the following fields before queueing
3051098217eSriastradh.Fa bp
3061098217eSriastradhwith
3071098217eSriastradh.Xr bufq_put 9 :
308*88fd7a1bSsevan.Bl -tag -width 6n -offset abcd
3091098217eSriastradh.It Fa bp Ns Li "->b_rawblkno"
3101098217eSriastradhBlock number relative to volume start.
3111098217eSriastradh.El
3121098217eSriastradh.Pp
3131098217eSriastradhWhen the I/O transfer is complete, whether it succeeded or failed, the
3141098217eSriastradhstrategy method must:
3151098217eSriastradh.Bl -dash
3161098217eSriastradh.It
3171098217eSriastradhSet
3181098217eSriastradh.Fa bp Ns Li "->b_error"
3191098217eSriastradhto zero on success, or to an
3201098217eSriastradh.Xr errno 2
3211098217eSriastradherror code on failure.
3221098217eSriastradh.It
3231098217eSriastradhSet
3241098217eSriastradh.Fa bp Ns Li "->b_resid"
3251098217eSriastradhto the number of bytes remaining to transfer, whether on success or
3261098217eSriastradhon failure.
3275586a441SriastradhIf no bytes were transferred, this must be set to
3281098217eSriastradh.Fa bp Ns Li "->b_bcount" .
3291098217eSriastradh.It
3301098217eSriastradhCall
331a6a845b4Sriastradh.Fn biodone bp .
3321098217eSriastradh.El
3331098217eSriastradh.Sh FUNCTIONS
3341098217eSriastradh.Bl -tag -width abcd
3351098217eSriastradh.It Fn biodone bp
3361098217eSriastradhNotify that the I/O transfer described by
3371098217eSriastradh.Fa bp
3381098217eSriastradhhas completed.
3391098217eSriastradh.Pp
3401098217eSriastradhTo be called by a block device driver.
3411098217eSriastradhCaller must first set
3421098217eSriastradh.Fa bp Ns Li "->b_error"
3431098217eSriastradhto an error code and
3441098217eSriastradh.Fa bp Ns Li "->b_resid"
3451098217eSriastradhto the number of bytes remaining to transfer.
3461098217eSriastradh.It Fn biowait bp
3471098217eSriastradhWait for the synchronous I/O transfer described by
3481098217eSriastradh.Fa bp
3491098217eSriastradhto complete.
3501098217eSriastradhReturns the value of
3511098217eSriastradh.Fa bp Ns Li "->b_error" .
3521098217eSriastradh.Pp
3531098217eSriastradhTo be called by a user requesting the I/O transfer.
3541098217eSriastradh.Pp
3551098217eSriastradhMay not be called if
3561098217eSriastradh.Fa bp
35788f86411Sriastradhhas a callback or is asynchronous \(em that is, if
358ba2aeff9Sriastradh.Fa bp Ns Li "->b_iodone"
359ba2aeff9Sriastradhis set, or if
3601098217eSriastradh.Dv B_ASYNC
3611098217eSriastradhis set in
3621098217eSriastradh.Fa bp Ns Li "->b_flags" .
3631098217eSriastradh.It Fn getiobuf vp waitok
3641098217eSriastradhAllocate a
365b2d5d8d7Sriastradh.Vt "struct buf"
3661098217eSriastradhfor an I/O transfer.
3671098217eSriastradhIf
3681098217eSriastradh.Fa vp
369078e20efSriastradhis
370078e20efSriastradh.Pf non- Dv NULL ,
371078e20efSriastradhthe transfer is associated with it.
3721098217eSriastradhIf
3731098217eSriastradh.Fa waitok
3741098217eSriastradhis false,
3756bcaafafSriastradhreturns
3766bcaafafSriastradh.Dv NULL
3776bcaafafSriastradhif none can be allocated immediately.
3781098217eSriastradh.Pp
379d05efdd6SriastradhThe resulting
380b2d5d8d7Sriastradh.Vt "struct buf"
381d05efdd6Sriastradhpointer must eventually be passed to
382d05efdd6Sriastradh.Fn putiobuf
383d05efdd6Sriastradhto release it.
384d05efdd6SriastradhDo
385d05efdd6Sriastradh.Em not
386d05efdd6Sriastradhuse
387d05efdd6Sriastradh.Xr brelse 9 .
388d05efdd6Sriastradh.Pp
389ba2aeff9SriastradhThe buffer may not be used for an asynchronous I/O transfer, because
390ba2aeff9Sriastradhthere is no way to know when it is completed and may be safely passed
391ba2aeff9Sriastradhto
392ba2aeff9Sriastradh.Fn putiobuf .
393ba2aeff9SriastradhAsynchronous I/O transfers are allowed only for buffers in the
394ba2aeff9Sriastradh.Xr buffercache 9 .
395ba2aeff9Sriastradh.Pp
3961098217eSriastradhMay sleep if
3971098217eSriastradh.Fa waitok
3981098217eSriastradhis true.
3991098217eSriastradh.It Fn putiobuf bp
4001098217eSriastradhFree
4011098217eSriastradh.Fa bp ,
4021098217eSriastradhwhich must have been allocated by
4031098217eSriastradh.Fn getiobuf .
4041098217eSriastradhEither
4051098217eSriastradh.Fa bp
4061098217eSriastradhmust never have been submitted to a block device, or the I/O transfer
4071098217eSriastradhmust have completed.
4081098217eSriastradh.El
4091098217eSriastradh.Sh CODE REFERENCES
4101098217eSriastradhThe
4111098217eSriastradh.Nm
4121098217eSriastradhsubsystem is implemented in
4131098217eSriastradh.Pa sys/kern/vfs_bio.c .
4141098217eSriastradh.Sh SEE ALSO
4151098217eSriastradh.Xr buffercache 9 ,
4161098217eSriastradh.Xr bufq 9
4171098217eSriastradh.Sh BUGS
4181098217eSriastradhThe
4191098217eSriastradh.Nm
42057ae0bf4Sriastradhabstraction provides no way to cancel an I/O transfer once it has been
4211098217eSriastradhsubmitted to a block device.
4221098217eSriastradh.Pp
4231098217eSriastradhThe
4241098217eSriastradh.Nm
4251098217eSriastradhabstraction provides no way to do I/O transfers with non-kernel pages,
4261098217eSriastradhe.g. directly to buffers in userland without copying into the kernel
4271098217eSriastradhfirst.
4281098217eSriastradh.Pp
4291098217eSriastradhThe
4301098217eSriastradh.Vt "struct buf"
4311098217eSriastradhtype is all mixed up with the
4321098217eSriastradh.Xr buffercache 9 .
4331098217eSriastradh.Pp
4341098217eSriastradhThe
4351098217eSriastradh.Nm
4361098217eSriastradhabstraction is a totally idiotic API design.
4371098217eSriastradh.Pp
4381098217eSriastradhThe
4391098217eSriastradh.Li v_numoutput
4401098217eSriastradhaccounting required of
4411098217eSriastradh.Nm
4421098217eSriastradhcallers is asinine.
443