1.\" $NetBSD: mbuf.9,v 1.57 2018/04/10 16:12:29 maxv Exp $ 2.\" 3.\" Copyright (c) 1997 The NetBSD Foundation, Inc. 4.\" All rights reserved. 5.\" 6.\" This documentation is derived from text contributed to The NetBSD Foundation 7.\" by S.P.Zeidler (aka stargazer). 8.\" 9.\" Redistribution and use in source and binary forms, with or without 10.\" modification, are permitted provided that the following conditions 11.\" are met: 12.\" 1. Redistributions of source code must retain the above copyright 13.\" notice, this list of conditions and the following disclaimer. 14.\" 2. Redistributions in binary form must reproduce the above copyright 15.\" notice, this list of conditions and the following disclaimer in the 16.\" documentation and/or other materials provided with the distribution. 17.\" 18.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28.\" POSSIBILITY OF SUCH DAMAGE. 29.\" 30.Dd April 10, 2018 31.Dt MBUF 9 32.Os 33.Sh NAME 34.Nm mbuf , 35.Nm m_get , 36.Nm m_gethdr , 37.Nm m_devget , 38.Nm m_copym , 39.Nm m_copypacket , 40.Nm m_copydata , 41.Nm m_copyback , 42.Nm m_copyback_cow , 43.Nm m_cat , 44.Nm m_dup , 45.Nm m_makewritable , 46.Nm m_prepend , 47.Nm m_pulldown , 48.Nm m_pullup , 49.Nm m_copyup , 50.Nm m_split , 51.Nm m_adj , 52.Nm m_apply , 53.Nm m_free , 54.Nm m_freem , 55.Nm mtod , 56.Nm MGET , 57.Nm MGETHDR , 58.Nm MEXTMALLOC , 59.Nm MEXTADD , 60.Nm MCLGET , 61.Nm M_COPY_PKTHDR , 62.Nm M_MOVE_PKTHDR , 63.Nm M_ALIGN , 64.Nm MH_ALIGN , 65.Nm M_LEADINGSPACE , 66.Nm M_TRAILINGSPACE , 67.Nm M_PREPEND , 68.Nm MCHTYPE 69.Nd "functions and macros for managing memory used by networking code" 70.Sh SYNOPSIS 71.In sys/mbuf.h 72.Ft struct mbuf * 73.Fn m_get "int nowait" "int type" 74.Ft struct mbuf * 75.Fn m_gethdr "int nowait" "int type" 76.Ft struct mbuf * 77.Fn m_devget "char *buf" "int totlen" "int off0" "struct ifnet *ifp" "void (*copy)(const void *, void *, size_t)" 78.Ft struct mbuf * 79.Fn m_copym "struct mbuf *m" "int off0" "int len" "int wait" 80.Ft struct mbuf * 81.Fn m_copypacket "struct mbuf *m" "int how" 82.Ft void 83.Fn m_copydata "struct mbuf *m" "int off" "int len" "void *cp" 84.Ft void 85.Fn m_copyback "struct mbuf *m0" "int off" "int len" "void *cp" 86.Ft struct mbuf * 87.Fn m_copyback_cow "struct mbuf *m0" "int off" "int len" "void *cp" "int how" 88.Ft int 89.Fn m_makewritable "struct mbuf **mp" "int off" "int len" "int how" 90.Ft void 91.Fn m_cat "struct mbuf *m" "struct mbuf *n" 92.Ft struct mbuf * 93.Fn m_dup "struct mbuf *m" "int off0" "int len" "int wait" 94.Ft struct mbuf * 95.Fn m_prepend "struct mbuf *m" "int len" "int how" 96.Ft struct mbuf * 97.Fn m_pulldown "struct mbuf *m" "int off" "int len" "int *offp" 98.Ft struct mbuf * 99.Fn m_pullup "struct mbuf *n" "int len" 100.Ft struct mbuf * 101.Fn m_copyup "struct mbuf *m" "int len" "int dstoff" 102.Ft struct mbuf * 103.Fn m_split "struct mbuf *m0" "int len0" "int wait" 104.Ft void 105.Fn m_adj "struct mbuf *mp" "int req_len" 106.Ft int 107.Fn m_apply "struct mbuf *m" "int off" "int len" "int *f(void *, void *, unsigned int)" "void *arg" 108.Ft struct mbuf * 109.Fn m_free "struct mbuf *m" 110.Ft void 111.Fn m_freem "struct mbuf *m" 112.Ft datatype 113.Fn mtod "struct mbuf *m" "datatype" 114.Ft void 115.Fn MGET "struct mbuf *m" "int how" "int type" 116.Ft void 117.Fn MGETHDR "struct mbuf *m" "int how" "int type" 118.Ft void 119.Fn MEXTMALLOC "struct mbuf *m" "int len" "int how" 120.Ft void 121.Fn MEXTADD "struct mbuf *m" "void *buf" "int size" "int type" "void (*free)(struct mbuf *, void *, size_t, void *)" "void *arg" 122.Ft void 123.Fn MCLGET "struct mbuf *m" "int how" 124.Ft void 125.Fn M_COPY_PKTHDR "struct mbuf *to" "struct mbuf *from" 126.Ft void 127.Fn M_MOVE_PKTHDR "struct mbuf *to" "struct mbuf *from" 128.Ft void 129.Fn M_ALIGN "struct mbuf *m" "int len" 130.Ft void 131.Fn MH_ALIGN "struct mbuf *m" "int len" 132.Ft int 133.Fn M_LEADINGSPACE "struct mbuf *m" 134.Ft int 135.Fn M_TRAILINGSPACE "struct mbuf *m" 136.Ft void 137.Fn M_PREPEND "struct mbuf *m" "int plen" "int how" 138.Ft void 139.Fn MCHTYPE "struct mbuf *m" "int type" 140.Sh DESCRIPTION 141The 142.Nm 143functions and macros provide an easy and consistent way to handle 144a networking stack's memory management needs. 145An 146.Nm 147consists of a header and a data area. 148It is of a fixed size, 149.Dv MSIZE 150.Pq defined in Aq Pa machine/param.h , 151which includes overhead. 152The header contains a pointer to the next 153.Nm 154in the 155.Sq "mbuf chain" , 156a pointer to the next 157.Sq "mbuf chain" , 158a pointer to the data area, the amount of data in this mbuf, its type 159and a 160.Dv flags 161field. 162.Pp 163The 164.Dv type 165variable can signify: 166.Bl -tag -compact -offset indent -width "XXXXXXXXXXX" 167.It Dv MT_FREE 168the mbuf should be on the ``free'' list 169.It Dv MT_DATA 170data was dynamically allocated 171.It Dv MT_HEADER 172data is a packet header 173.It Dv MT_SONAME 174data is a socket name 175.It Dv MT_SOOPTS 176data is socket options 177.It Dv MT_FTABLE 178data is the fragment reassembly header 179.It Dv MT_CONTROL 180mbuf contains ancillary \&(protocol control\&) data 181.It Dv MT_OOBDATA 182mbuf contains out-of-band data. 183.El 184.Pp 185The 186.Dv flags 187variable contains information describing the 188.Nm , 189notably: 190.Bl -tag -compact -offset indent -width "XXXXXXXXXXX" 191.It Dv M_EXT 192has external storage 193.It Dv M_PKTHDR 194is start of record 195.It Dv M_EOR 196is end of record 197.It Dv M_CLUSTER 198external storage is a cluster. 199.El 200.Pp 201If an 202.Nm 203designates the start of a record 204.Pq Dv M_PKTHDR , 205its 206.Dv flags 207field may contain additional information describing the content of 208the record: 209.Bl -tag -compact -offset indent -width "XXXXXXXXXXX" 210.It Dv M_BCAST 211sent/received as link-level broadcast 212.It Dv M_MCAST 213sent/received as link-level multicast 214.It Dv M_LINK0 , 215.It Dv M_LINK1 , 216.It Dv M_LINK2 217three link-level specific flags. 218.El 219.Pp 220An 221.Nm 222may add a single 223.Sq "mbuf cluster" 224of 225.Dv MCLBYTES 226bytes 227.Pq also defined in Aq Pa machine/param.h , 228which has no additional overhead 229and is used instead of the internal data area; this is done when at least 230.Dv MINCLSIZE 231bytes of data must be stored. 232.Pp 233When the 234.Dv M_EXT 235flag is raised for an mbuf, 236the external storage area could be shared among multiple mbufs. 237Be careful when you attempt to overwrite the data content of the mbuf. 238.Bl -tag -width compact 239.It Fn m_get "int nowait" "int type" 240Allocates an mbuf and initializes it to contain internal data. 241The 242.Fa nowait 243parameter is a choice of 244.Dv M_WAIT / M_DONTWAIT 245from caller. 246.Dv M_WAIT 247means the call cannot fail, but may take forever. 248The 249.Fa type 250parameter is an mbuf type. 251.It Fn m_gethdr "int nowait" "int type" 252Allocates an mbuf and initializes it to contain a packet header and internal 253data. 254The 255.Fa nowait 256parameter is a choice of 257.Dv M_WAIT / M_DONTWAIT 258from caller. 259The 260.Fa type 261parameter is an mbuf type. 262.It Fn m_devget "char *buf" "int totlen" "int off0" "struct ifnet *ifp" "void (*copy)(const void *, void *, size_t)" 263Copies 264.Fa len 265bytes from device local memory into mbufs using copy routine 266.Fa copy . 267If parameter 268.Fa off 269is non-zero, the packet is supposed to be trailer-encapsulated and 270.Fa off 271bytes plus the type and length fields will be skipped before copying. 272Returns the top of the mbuf chain it created. 273.It Fn m_copym "struct mbuf *m" "int off0" "int len" "int wait" 274Creates a copy of an mbuf chain starting 275.Fa off0 276bytes from the beginning, continuing for 277.Fa len 278bytes. 279If the 280.Fa len 281requested is 282.Dv M_COPYALL , 283the complete mbuf chain will be copied. 284The 285.Fa wait 286parameter is a choice of 287.Dv M_WAIT / M_DONTWAIT 288from caller. 289.It Fn m_copypacket "struct mbuf *m" "int how" 290Copies an entire packet, including header (which must be present). 291This function is an optimization of the common case 292.Li m_copym ( m , 0 , Dv M_COPYALL , Fa how ) . 293.It Fn m_copydata "struct mbuf *m" "int off" "int len" "void *cp" 294Copies 295.Fa len 296bytes data from mbuf chain 297.Fa m 298into the buffer 299.Fa cp , 300starting 301.Fa off 302bytes from the beginning. 303.It Fn m_copyback "struct mbuf *m0" "int off" "int len" "void *cp" 304Copies 305.Fa len 306bytes data from buffer 307.Fa cp 308back into the mbuf chain 309.Fa m0 , 310starting 311.Fa off 312bytes from the beginning of the chain, extending the mbuf chain if necessary. 313.Fn m_copyback 314can only fail when extending the chain. 315The caller should check for this kind of failure 316by checking the resulting length of the chain in that case. 317It is an error to use 318.Fn m_copyback 319on read-only mbufs. 320.It Fn m_copyback_cow "struct mbuf *m0" "int off" "int len" "void *cp" \ 321"int how" 322Copies 323.Fa len 324bytes data from buffer 325.Fa cp 326back into the mbuf chain 327.Fa m0 328as 329.Fn m_copyback 330does. 331Unlike 332.Fn m_copyback , 333it is safe to use 334.Fn m_copyback_cow 335on read-only mbufs. 336If needed, 337.Fn m_copyback_cow 338automatically allocates new mbufs and adjusts the chain. 339On success, it returns a pointer to the resulting mbuf chain, 340and frees the original mbuf 341.Fa m0 . 342Otherwise, it returns 343.Dv NULL . 344The 345.Fa how 346parameter is a choice of 347.Dv M_WAIT / M_DONTWAIT 348from the caller. 349Unlike 350.Fn m_copyback , 351extending the mbuf chain isn't supported. 352It is an error to attempt to extend the mbuf chain using 353.Fn m_copyback_cow . 354.It Fn m_makewritable "struct mbuf **mp" "int off" "int len" "int how" 355Rearranges an mbuf chain so that 356.Fa len 357bytes from offset 358.Fa off 359are writable. 360When it meets read-only mbufs, it allocates new mbufs, adjusts the chain as 361.Fn m_copyback_cow 362does, and copies the original content into them. 363.Fn m_makewritable 364does 365.Em not 366guarantee that all 367.Fa len 368bytes at 369.Fa off 370are consecutive. 371The 372.Fa how 373parameter is a choice of 374.Dv M_WAIT / M_DONTWAIT 375from the caller. 376.Fn m_makewritable 377preserves the contents of the mbuf chain even in the case of failure. 378It updates a pointer to the mbuf chain pointed to by 379.Fa mp . 380It returns 0 on success. 381Otherwise, it returns an error code, typically 382.Er ENOBUFS . 383.It Fn m_cat "struct mbuf *m" "struct mbuf *n" 384Concatenates mbuf chain 385.Fa n 386to 387.Fa m . 388Both chains must be of the same type; packet headers will 389.Em not 390be updated if present. 391.It Fn m_dup "struct mbuf *m" "int off0" "int len" "int wait" 392Similarly to 393.Fn m_copym , 394the function creates a copy of an mbuf chain starting 395.Fa off0 396bytes from the beginning, continuing for 397.Fa len 398bytes. 399While 400.Fn m_copym 401tries to share external storage for mbufs with 402.Dv M_EXT 403flag, 404.Fn m_dup 405will deep-copy the whole data content into new mbuf chain 406and avoids shared external storage. 407.It Fn m_prepend "struct mbuf *m" "int len" "int how" 408Lesser-used path for 409.Fn M_PREPEND : 410allocates new mbuf 411.Fa m 412of size 413.Fa len 414to prepend to the chain, copying junk along. 415The 416.Fa how 417parameter is a choice of 418.Dv M_WAIT / M_DONTWAIT 419from caller. 420It is illegal for the 421.Fa len 422parameter to be greater than 423.Dv MHLEN . 424.It Fn m_pulldown "struct mbuf *m" "int off" "int len" "int *offp" 425Rearranges an mbuf chain so that 426.Fa len 427bytes from offset 428.Fa off 429are contiguous and in the data area of an mbuf. 430The return value points to an mbuf in the middle of the mbuf chain 431.Fa m . 432If we call the return value 433.Fa n , 434the contiguous data region is available at 435.Li "mtod(n, void *) + *offp" , 436or 437.Li "mtod(n, void *)" 438if 439.Fa offp 440is 441.Dv NULL . 442The top of the mbuf chain 443.Fa m , 444and mbufs up to 445.Fa off , 446will not be modified. 447On successful return, it is guaranteed that the mbuf pointed to by 448.Fa n 449does not have a shared external storage, 450therefore it is safe to update the contiguous region. 451Returns 452.Dv NULL 453and frees the mbuf chain on failure. 454.Fa len 455must be smaller than or equal to 456.Dv MCLBYTES . 457.It Fn m_pullup "struct mbuf *m" "int len" 458Rearranges an mbuf chain so that 459.Fa len 460bytes are contiguous 461and in the data area of an mbuf (so that 462.Fn mtod 463will work for a structure of size 464.Fa len ) . 465Returns the resulting 466mbuf chain on success, frees it and returns 467.Dv NULL 468on failure. 469If there is room, it will add up to 470.Dv max_protohdr 471- 472.Fa len 473extra bytes to the 474contiguous region to possibly avoid being called again. 475.Fa len 476must be smaller or equal than 477.Dv MHLEN . 478.It Fn m_copyup "struct mbuf *m" "int len" "int dstoff" 479Similar to 480.Fn m_pullup 481but copies 482.Fa len 483bytes of data into a new mbuf at 484.Fa dstoff 485bytes into the mbuf. 486The 487.Fa dstoff 488argument aligns the data and leaves room for a link layer header. 489Returns the new 490mbuf chain on success, and frees the mbuf chain and returns 491.Dv NULL 492on failure. 493Note that 494the function does not allocate mbuf clusters, so 495.Fa len + dstoff 496must be less than 497.Dv MHLEN . 498.It Fn m_split "struct mbuf *m0" "int len0" "int wait" 499Partitions an mbuf chain in two pieces, returning the tail, 500which is all but the first 501.Fa len0 502bytes. 503In case of failure, it returns 504.Dv NULL 505and restores the chain to its original state. 506.It Fn m_adj "struct mbuf *mp" "int req_len" 507Shaves off 508.Fa req_len 509bytes from head or tail of the (valid) data area. 510If 511.Fa req_len 512is greater than zero, front bytes are being shaved off, if it's smaller, 513from the back (and if it is zero, the mbuf will stay bearded). 514This function does not move data in any way, but is used to manipulate the 515data area pointer and data length variable of the mbuf in a non-clobbering 516way. 517.It Fn m_apply "struct mbuf *m" "int off" "int len" "int (*f)(void *, void *, unsigned int)" "void *arg" 518Apply function 519.Fa f 520to the data in an mbuf chain starting 521.Fa off 522bytes from the beginning, continuing for 523.Fa len 524bytes. 525Neither 526.Fa off 527nor 528.Fa len 529may be negative. 530.Fa arg 531will be supplied as first argument for 532.Fa f , 533the second argument will be the pointer to the data buffer of a 534packet (starting after 535.Fa off 536bytes in the stream), and the third argument is the amount 537of data in bytes in this call. 538If 539.Fa f 540returns something not equal to zero 541.Fn m_apply 542will bail out, returning the return code of 543.Fa f . 544Upon successful completion it will return zero. 545.It Fn m_free "struct mbuf *m" 546Frees mbuf 547.Fa m . 548.It Fn m_freem "struct mbuf *m" 549Frees the mbuf chain beginning with 550.Fa m . 551This function contains the elementary sanity check for a 552.Dv NULL 553pointer. 554.It Fn mtod "struct mbuf *m" "datatype" 555Returns a pointer to the data contained in the specified mbuf 556.Fa m , 557type-casted to the specified data type 558.Fa datatype . 559Implemented as a macro. 560.It Fn MGET "struct mbuf *m" "int how" "int type" 561Allocates mbuf 562.Fa m 563and initializes it to contain internal data. 564See 565.Fn m_get . 566Implemented as a macro. 567.It Fn MGETHDR "struct mbuf *m" "int how" "int type" 568Allocates mbuf 569.Fa m 570and initializes it to contain a packet header. 571See 572.Fn m_gethdr . 573Implemented as a macro. 574.It Fn MEXTMALLOC "struct mbuf *m" "int len" "int how" 575Allocates external storage of size 576.Fa len 577for mbuf 578.Fa m . 579The 580.Fa how 581parameter is a choice of 582.Dv M_WAIT / M_DONTWAIT 583from caller. 584The flag 585.Dv M_EXT 586is set upon success. 587Implemented as a macro. 588.It Fn MEXTADD "struct mbuf *m" "void *buf" "int size" "int type" "void (*free)(struct mbuf *, void *, size_t, void *)" "void *arg" 589Adds pre-allocated external storage 590.Fa buf 591to a normal mbuf 592.Fa m ; 593the parameters 594.Fa size , 595.Fa type , 596.Fa free 597and 598.Fa arg 599describe the external storage. 600.Fa size 601is the size of the storage, 602.Fa type 603describes its 604.Xr malloc 9 605type, 606.Fa free 607is a free routine (if not the usual one), and 608.Fa arg 609is a possible argument to the free routine. 610The flag 611.Dv M_EXT 612is set upon success. 613Implemented as a macro. 614If a free routine is specified, it will be called when the mbuf is freed. 615In the case of former, the first argument for a free routine is the mbuf 616.Fa m 617and the routine is expected to free it in addition to the external storage 618pointed by second argument. 619In the case of latter, the first argument for the routine is NULL. 620.It Fn MCLGET "struct mbuf *m" "int how" 621Allocates and adds an mbuf cluster to a normal mbuf 622.Fa m . 623The 624.Fa how 625parameter is a choice of 626.Dv M_WAIT / M_DONTWAIT 627from caller. 628The flag 629.Dv M_EXT 630is set upon success. 631Implemented as a macro. 632.It Fn M_COPY_PKTHDR "struct mbuf *to" "struct mbuf *from" 633Copies the mbuf pkthdr from mbuf 634.Fa from 635to mbuf 636.Fa to . 637.Fa from 638must have the type flag 639.Dv M_PKTHDR 640set, and 641.Fa to 642must be empty. 643Implemented as a macro. 644.It Fn M_MOVE_PKTHDR "struct mbuf *to" "struct mbuf *from" 645Moves the mbuf pkthdr from mbuf 646.Fa from 647to mbuf 648.Fa to . 649.Fa from 650must have the type flag 651.Dv M_PKTHDR 652set, and 653.Fa to 654must be empty. 655The flag 656.Dv M_PKTHDR 657in mbuf 658.Fa from 659will be cleared. 660.It Fn M_ALIGN "struct mbuf *m" "int len" 661Sets the data pointer of a newly allocated mbuf 662.Fa m 663to 664.Fa len 665bytes from the end of the mbuf data area, so that 666.Fa len 667bytes of data written to the mbuf 668.Fa m , 669starting at the data pointer, will be aligned to the end of the data area. 670Implemented as a macro. 671.It Fn MH_ALIGN "struct mbuf *m" "int len" 672Sets the data pointer of a newly allocated packetheader mbuf 673.Fa m 674to 675.Fa len 676bytes from the end of the mbuf data area, so that 677.Fa len 678bytes of data written to the mbuf 679.Fa m , 680starting at the data pointer, will be aligned to the end of the data area. 681Implemented as a macro. 682.It Fn M_LEADINGSPACE "struct mbuf *m" 683Returns the amount of space available before the current start of valid 684data in mbuf 685.Fa m . 686Returns 0 if the mbuf data part is shared across multiple mbufs 687.Pq i.e. not writable . 688Implemented as a macro. 689.It Fn M_TRAILINGSPACE "struct mbuf *m" 690Returns the amount of space available after the current end of valid 691data in mbuf 692.Fa m . 693Returns 0 if the mbuf data part is shared across multiple mbufs 694.Pq i.e. not writable . 695Implemented as a macro. 696.It Fn M_PREPEND "struct mbuf *m" "int plen" "int how" 697Prepends space of size 698.Fa plen 699to mbuf 700.Fa m . 701If a new mbuf must be allocated, 702.Fa how 703specifies whether to wait. 704If 705.Fa how 706is 707.Dv M_DONTWAIT 708and allocation fails, the original mbuf chain is freed and 709.Fa m 710is set to 711.Dv NULL . 712Implemented as a macro. 713.It Fn MCHTYPE "struct mbuf *m" "int type" 714Change mbuf 715.Fa m 716to new type 717.Fa type . 718Implemented as a macro. 719.El 720.Sh CODE REFERENCES 721The 722.Nm 723management functions are implemented within the file 724.Pa sys/kern/uipc_mbuf.c . 725Function prototypes, and the functions implemented as macros 726are located in 727.Pa sys/sys/mbuf.h . 728.Sh SEE ALSO 729.Pa /usr/share/doc/smm/18.net , 730.Xr netstat 1 , 731.Xr m_tag 9 , 732.Xr malloc 9 733.Rs 734.%A Jun-ichiro Hagino 735.%T "Mbuf issues in 4.4BSD IPv6/IPsec support (experiences from KAME IPv6/IPsec implementation)" 736.%B "Proceedings of the freenix track: 2000 USENIX annual technical conference" 737.%D June 2000 738.Re 739.Sh AUTHORS 740.An -nosplit 741The original mbuf data structures were designed by Rob Gurwitz 742when he did the initial TCP/IP implementation at BBN. 743.Pp 744Further extensions and enhancements were made by Bill Joy, Sam Leffler, 745and Mike Karels at CSRG. 746.Pp 747Current implementation of external storage by 748.An Matt Thomas 749.Aq matt@3am-software.com 750and 751.An Jason R. Thorpe 752.Aq thorpej@NetBSD.org . 753