1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
|
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lisafs
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sync"
)
// FDID (file descriptor identifier) is used to identify FDs on a connection.
// Each connection has its own FDID namespace.
//
// +marshal boundCheck slice:FDIDSlice
type FDID uint64
// InvalidFDID represents an invalid FDID.
const InvalidFDID FDID = 0
// Ok returns true if f is a valid FDID.
func (f FDID) Ok() bool {
return f != InvalidFDID
}
// genericFD can represent any type of FD.
type genericFD interface {
refs.RefCounter
}
// A ControlFD is the gateway to the backing filesystem tree node. It is an
// unusual concept. This exists to provide a safe way to do path-based
// operations on the file. It performs operations that can modify the
// filesystem tree and synchronizes these operations. See ControlFDImpl for
// supported operations.
//
// It is not an inode, because multiple control FDs are allowed to exist on the
// same file. It is not a file descriptor because it is not tied to any access
// mode, i.e. a control FD can change its access mode based on the operation
// being performed.
//
// Reference Model:
// - Each control FD holds a ref on its Node for its entire lifetime.
type ControlFD struct {
controlFDRefs
controlFDEntry
// node is the filesystem node this FD is immutably associated with.
node *Node
// openFDs is a linked list of all FDs opened on this FD. As per reference
// model, all open FDs hold a ref on this FD.
openFDsMu sync.RWMutex
openFDs openFDList
// All the following fields are immutable.
// id is the unique FD identifier which identifies this FD on its connection.
id FDID
// conn is the backing connection owning this FD.
conn *Connection
// ftype is the file type of the backing inode. ftype.FileType() == ftype.
ftype linux.FileMode
// impl is the control FD implementation which embeds this struct. It
// contains all the implementation specific details.
impl ControlFDImpl
}
var _ genericFD = (*ControlFD)(nil)
// DecRef implements refs.RefCounter.DecRef. Note that the context
// parameter should never be used. It exists solely to comply with the
// refs.RefCounter interface.
func (fd *ControlFD) DecRef(context.Context) {
fd.controlFDRefs.DecRef(func() {
fd.conn.server.renameMu.RLock()
defer fd.conn.server.renameMu.RUnlock()
fd.destroyLocked()
})
}
// decRefLocked is the same as DecRef except the added precondition.
//
// Precondition: server's rename mutex must be at least read locked.
func (fd *ControlFD) decRefLocked() {
fd.controlFDRefs.DecRef(func() {
fd.destroyLocked()
})
}
// Precondition: server's rename mutex must be at least read locked.
func (fd *ControlFD) destroyLocked() {
// Update node's control FD list.
fd.node.removeFD(fd)
// Drop ref on node.
fd.node.DecRef(nil)
// Let the FD implementation clean up.
fd.impl.Close()
}
// Init must be called before first use of fd. It inserts fd into the
// filesystem tree.
//
// Preconditions:
// - server's rename mutex must be at least read locked.
// - The caller must take a ref on node which is transferred to fd.
func (fd *ControlFD) Init(c *Connection, node *Node, mode linux.FileMode, impl ControlFDImpl) {
fd.conn = c
fd.node = node
fd.impl = impl
fd.ftype = mode.FileType()
// Initialize fd with 1 ref which is transferred to c via c.insertFD().
fd.controlFDRefs.InitRefs()
// Make fd reachable/discoverable.
fd.id = c.insertFD(fd)
node.insertFD(fd)
}
// Conn returns the fd's owning connection.
func (fd *ControlFD) Conn() *Connection {
return fd.conn
}
// FileType returns the file mode only containing the file type bits.
func (fd *ControlFD) FileType() linux.FileMode {
return fd.ftype
}
// IsDir indicates whether fd represents a directory.
func (fd *ControlFD) IsDir() bool {
return fd.ftype == unix.S_IFDIR
}
// IsRegular indicates whether fd represents a regular file.
func (fd *ControlFD) IsRegular() bool {
return fd.ftype == unix.S_IFREG
}
// IsSymlink indicates whether fd represents a symbolic link.
func (fd *ControlFD) IsSymlink() bool {
return fd.ftype == unix.S_IFLNK
}
// IsSocket indicates whether fd represents a socket.
func (fd *ControlFD) IsSocket() bool {
return fd.ftype == unix.S_IFSOCK
}
// Node returns the node this FD was opened on.
func (fd *ControlFD) Node() *Node {
return fd.node
}
// RemoveFromConn removes this control FD from its owning connection.
//
// Preconditions:
// - fd should not have been returned to the client. Otherwise the client can
// still refer to it.
// - server's rename mutex must at least be read locked.
func (fd *ControlFD) RemoveFromConn() {
fd.conn.removeControlFDLocked(fd.id)
}
// safelyRead executes the given operation with the local path node locked.
// This guarantees that fd's path will not change. fn may not any change paths.
func (fd *ControlFD) safelyRead(fn func() error) error {
fd.conn.server.renameMu.RLock()
defer fd.conn.server.renameMu.RUnlock()
fd.node.opMu.RLock()
defer fd.node.opMu.RUnlock()
return fn()
}
// safelyWrite executes the given operation with the local path node locked in
// a writable fashion. This guarantees that no other operation is executing on
// this path node. fn may change paths inside fd.node.
func (fd *ControlFD) safelyWrite(fn func() error) error {
fd.conn.server.renameMu.RLock()
defer fd.conn.server.renameMu.RUnlock()
fd.node.opMu.Lock()
defer fd.node.opMu.Unlock()
return fn()
}
// safelyGlobal executes the given operation with the global path lock held.
// This guarantees that no other operations is executing concurrently on this
// server. fn may change any path.
func (fd *ControlFD) safelyGlobal(fn func() error) (err error) {
fd.conn.server.renameMu.Lock()
defer fd.conn.server.renameMu.Unlock()
return fn()
}
// forEachOpenFD executes fn on each FD opened on fd.
func (fd *ControlFD) forEachOpenFD(fn func(ofd *OpenFD)) {
fd.openFDsMu.RLock()
defer fd.openFDsMu.RUnlock()
for ofd := fd.openFDs.Front(); ofd != nil; ofd = ofd.Next() {
fn(ofd)
}
}
// OpenFD represents an open file descriptor on the protocol. It resonates
// closely with a Linux file descriptor. Its operations are limited to the
// file. Its operations are not allowed to modify or traverse the filesystem
// tree. See OpenFDImpl for the supported operations.
//
// Reference Model:
// - An OpenFD takes a reference on the control FD it was opened on.
type OpenFD struct {
openFDRefs
openFDEntry
// All the following fields are immutable.
// controlFD is the ControlFD on which this FD was opened. OpenFD holds a ref
// on controlFD for its entire lifetime.
controlFD *ControlFD
// id is the unique FD identifier which identifies this FD on its connection.
id FDID
// Access mode for this FD.
readable bool
writable bool
// impl is the open FD implementation which embeds this struct. It
// contains all the implementation specific details.
impl OpenFDImpl
}
var _ genericFD = (*OpenFD)(nil)
// ControlFD returns the control FD on which this FD was opened.
func (fd *OpenFD) ControlFD() ControlFDImpl {
return fd.controlFD.impl
}
// DecRef implements refs.RefCounter.DecRef. Note that the context
// parameter should never be used. It exists solely to comply with the
// refs.RefCounter interface.
func (fd *OpenFD) DecRef(context.Context) {
fd.openFDRefs.DecRef(func() {
fd.controlFD.openFDsMu.Lock()
fd.controlFD.openFDs.Remove(fd)
fd.controlFD.openFDsMu.Unlock()
fd.controlFD.DecRef(nil) // Drop the ref on the control FD.
fd.impl.Close()
})
}
// Init must be called before first use of fd.
func (fd *OpenFD) Init(cfd *ControlFD, flags uint32, impl OpenFDImpl) {
// Initialize fd with 1 ref which is transferred to c via c.insertFD().
fd.openFDRefs.InitRefs()
fd.controlFD = cfd
fd.id = cfd.conn.insertFD(fd)
accessMode := flags & unix.O_ACCMODE
fd.readable = accessMode == unix.O_RDONLY || accessMode == unix.O_RDWR
fd.writable = accessMode == unix.O_WRONLY || accessMode == unix.O_RDWR
fd.impl = impl
cfd.IncRef() // Holds a ref on cfd for its lifetime.
cfd.openFDsMu.Lock()
cfd.openFDs.PushBack(fd)
cfd.openFDsMu.Unlock()
}
// BoundSocketFD represents a bound socket on the server.
//
// Reference Model:
// - A BoundSocketFD takes a reference on the control FD it is bound to.
type BoundSocketFD struct {
boundSocketFDRefs
// All the following fields are immutable.
// controlFD is the ControlFD on which this FD was bound. BoundSocketFD
// holds a ref on controlFD for its entire lifetime.
controlFD *ControlFD
// id is the unique FD identifier which identifies this FD on its connection.
id FDID
// impl is the socket FD implementation which embeds this struct. It
// contains all the implementation specific details.
impl BoundSocketFDImpl
}
var _ genericFD = (*BoundSocketFD)(nil)
// ControlFD returns the control FD on which this FD was bound.
func (fd *BoundSocketFD) ControlFD() ControlFDImpl {
return fd.controlFD.impl
}
// DecRef implements refs.RefCounter.DecRef. Note that the context
// parameter should never be used. It exists solely to comply with the
// refs.RefCounter interface.
func (fd *BoundSocketFD) DecRef(context.Context) {
fd.boundSocketFDRefs.DecRef(func() {
fd.controlFD.DecRef(nil) // Drop the ref on the control FD.
fd.impl.Close()
})
}
// Init must be called before first use of fd.
func (fd *BoundSocketFD) Init(cfd *ControlFD, impl BoundSocketFDImpl) {
// Initialize fd with 1 ref which is transferred to c via c.insertFD().
fd.boundSocketFDRefs.InitRefs()
fd.controlFD = cfd
fd.id = cfd.conn.insertFD(fd)
fd.impl = impl
cfd.IncRef() // Holds a ref on cfd for its lifetime.
}
// There are four different types of guarantees provided:
//
// none: There is no concurrency guarantee. The method may be invoked
// concurrently with any other method on any other FD.
//
// read: The method is guaranteed to be exclusive of any write or global
// operation that is mutating the state of the directory tree starting at this
// node. For example, this means creating new files, symlinks, directories or
// renaming a directory entry (or renaming in to this target), but the method
// may be called concurrently with other read methods.
//
// write: The method is guaranteed to be exclusive of any read, write or global
// operation that is mutating the state of the directory tree starting at this
// node, as described in read above. There may however, be other write
// operations executing concurrently on other components in the directory tree.
//
// global: The method is guaranteed to be exclusive of any read, write or
// global operation.
// ControlFDImpl contains implementation details for a ControlFD.
// Implementations of ControlFDImpl should contain their associated ControlFD
// by value as their first field.
//
// The operations that perform path traversal or any modification to the
// filesystem tree must synchronize those modifications with the server's
// rename mutex.
type ControlFDImpl interface {
// FD returns a pointer to the embedded ControlFD.
FD() *ControlFD
// Close should clean up resources used by the control FD implementation.
// Close is called after all references on the FD have been dropped and its
// FDID has been released.
//
// On the server, Close has no concurrency guarantee.
Close()
// Stat returns the stat(2) results for this FD.
//
// On the server, Stat has a read concurrency guarantee.
Stat() (linux.Statx, error)
// SetStat sets file attributes on the backing file. This does not correspond
// to any one Linux syscall. On Linux, this operation is performed using
// multiple syscalls like fchmod(2), fchown(2), ftruncate(2), futimesat(2)
// and so on. The implementation must only set attributes for fields
// indicated by stat.Mask. Failure to set an attribute may or may not
// terminate the entire operation. SetStat must return a uint32 which is
// interpreted as a stat mask to indicate which attribute setting attempts
// failed. If multiple attribute setting attempts failed, the returned error
// may be from any one of them.
//
// On the server, SetStat has a write concurrency guarantee.
SetStat(stat SetStatReq) (uint32, error)
// Walk walks one path component from the directory represented by this FD.
// Walk must open a ControlFD on the walked file.
//
// On the server, Walk has a read concurrency guarantee.
Walk(name string) (*ControlFD, linux.Statx, error)
// WalkStat is capable of walking multiple path components and returning the
// stat results for each path component walked via recordStat. Stat results
// must be returned in the order of walk.
//
// In case a symlink is encountered, the walk must terminate successfully on
// the symlink including its stat result.
//
// The first path component of path may be "" which indicates that the first
// stat result returned must be of this starting directory.
//
// On the server, WalkStat has a read concurrency guarantee.
WalkStat(path StringArray, recordStat func(linux.Statx)) error
// Open opens the control FD with the flags passed. The flags should be
// interpreted as open(2) flags.
//
// Open may also optionally return a host FD for the opened file whose
// lifecycle is independent of the OpenFD. Returns -1 if not available.
//
// N.B. The server must resolve any lazy paths when open is called.
// After this point, read and write may be called on files with no
// deletion check, so resolving in the data path is not viable.
//
// On the server, Open has a read concurrency guarantee.
Open(flags uint32) (*OpenFD, int, error)
// OpenCreate creates a regular file inside the directory represented by this
// FD and then also opens the file. The created file has perms as specified
// by mode and owners as specified by uid and gid. The file is opened with
// the specified flags.
//
// OpenCreate may also optionally return a host FD for the opened file whose
// lifecycle is independent of the OpenFD. Returns -1 if not available.
//
// N.B. The server must resolve any lazy paths when open is called.
// After this point, read and write may be called on files with no
// deletion check, so resolving in the data path is not viable.
//
// On the server, OpenCreate has a write concurrency guarantee.
OpenCreate(mode linux.FileMode, uid UID, gid GID, name string, flags uint32) (*ControlFD, linux.Statx, *OpenFD, int, error)
// Mkdir creates a directory inside the directory represented by this FD. The
// created directory has perms as specified by mode and owners as specified
// by uid and gid.
//
// On the server, Mkdir has a write concurrency guarantee.
Mkdir(mode linux.FileMode, uid UID, gid GID, name string) (*ControlFD, linux.Statx, error)
// Mknod creates a file inside the directory represented by this FD. The file
// type and perms are specified by mode and owners are specified by uid and
// gid. If the newly created file is a character or block device, minor and
// major specify its device number.
//
// On the server, Mkdir has a write concurrency guarantee.
Mknod(mode linux.FileMode, uid UID, gid GID, name string, minor uint32, major uint32) (*ControlFD, linux.Statx, error)
// Symlink creates a symlink inside the directory represented by this FD. The
// symlink has owners as specified by uid and gid and points to target.
//
// On the server, Symlink has a write concurrency guarantee.
Symlink(name string, target string, uid UID, gid GID) (*ControlFD, linux.Statx, error)
// Link creates a hard link to the file represented by this FD. The hard link
// is created inside dir with the specified name.
//
// On the server, Link has a write concurrency guarantee for dir and read
// concurrency guarantee for this file.
Link(dir ControlFDImpl, name string) (*ControlFD, linux.Statx, error)
// StatFS returns information about the file system associated with
// this file.
//
// On the server, StatFS has read concurrency guarantee.
StatFS() (StatFS, error)
// Readlink reads the symlink's target and writes the string into the buffer
// returned by getLinkBuf which can be used to request buffer for some size.
// It returns the number of bytes written into the buffer.
//
// On the server, Readlink has a read concurrency guarantee.
Readlink(getLinkBuf func(uint32) []byte) (uint16, error)
// Connect establishes a new host-socket backed connection with a unix domain
// socket. On success it returns a non-blocking host socket FD whose
// lifecycle is independent of this ControlFD.
//
// sockType indicates the requested type of socket and can be passed as type
// argument to socket(2).
//
// On the server, Connect has a read concurrency guarantee.
Connect(sockType uint32) (int, error)
// BindAt creates a host unix domain socket of type sockType, bound to
// the given namt of type sockType, bound to the given name. It returns
// a ControlFD that can be used for path operations on the socket, a
// BoundSocketFD that can be used to Accept/Listen on the socket, and a
// host FD that can be used for event notifications (like new
// connections).
//
// On the server, BindAt has a write concurrency guarantee.
BindAt(name string, sockType uint32, mode linux.FileMode, uid UID, gid GID) (*ControlFD, linux.Statx, *BoundSocketFD, int, error)
// UnlinkAt the file identified by name in this directory.
//
// Flags are Linux unlinkat(2) flags.
//
// On the server, UnlinkAt has a write concurrency guarantee.
Unlink(name string, flags uint32) error
// RenameAt renames a given file to a new name in a potentially new directory.
//
// oldName must be a name relative to this file, which must be a directory.
// newName is a name relative to newDir.
//
// On the server, RenameAt has a global concurrency guarantee.
RenameAt(oldName string, newDir ControlFDImpl, newName string) error
// Renamed is called to notify the FD implementation that the file has been
// renamed. FD implementation may update its state accordingly.
//
// On the server, Renamed has a global concurrency guarantee.
Renamed()
// GetXattr returns extended attributes of this file. It returns the number
// of bytes written into the buffer returned by getValueBuf which can be used
// to request buffer for some size.
//
// If the value is larger than size, implementations may return ERANGE to
// indicate that the buffer is too small.
//
// N.B. size may be 0, in which can the implementation must first find out
// the attribute value size using getxattr(2) by passing size=0. Then request
// a buffer large enough using getValueBuf and write the value there.
//
// On the server, GetXattr has a read concurrency guarantee.
GetXattr(name string, size uint32, getValueBuf func(uint32) []byte) (uint16, error)
// SetXattr sets extended attributes on this file.
//
// On the server, SetXattr has a write concurrency guarantee.
SetXattr(name string, value string, flags uint32) error
// ListXattr lists the names of the extended attributes on this file.
//
// Size indicates the size of the buffer that has been allocated to hold the
// attribute list. If the list would be larger than size, implementations may
// return ERANGE to indicate that the buffer is too small, but they are also
// free to ignore the hint entirely (i.e. the value returned may be larger
// than size). All size checking is done independently at the syscall layer.
//
// On the server, ListXattr has a read concurrency guarantee.
ListXattr(size uint64) (StringArray, error)
// RemoveXattr removes extended attributes on this file.
//
// On the server, RemoveXattr has a write concurrency guarantee.
RemoveXattr(name string) error
}
// OpenFDImpl contains implementation details for a OpenFD. Implementations of
// OpenFDImpl should contain their associated OpenFD by value as their first
// field.
//
// Since these operations do not perform any path traversal or any modification
// to the filesystem tree, there is no need to synchronize with rename
// operations.
type OpenFDImpl interface {
// FD returns a pointer to the embedded OpenFD.
FD() *OpenFD
// Close should clean up resources used by the open FD implementation.
// Close is called after all references on the FD have been dropped and its
// FDID has been released.
//
// On the server, Close has no concurrency guarantee.
Close()
// Stat returns the stat(2) results for this FD.
//
// On the server, Stat has a read concurrency guarantee.
Stat() (linux.Statx, error)
// Sync is simialr to fsync(2).
//
// On the server, Sync has a read concurrency guarantee.
Sync() error
// Write writes buf at offset off to the backing file via this open FD. Write
// attempts to write len(buf) bytes and returns the number of bytes written.
//
// On the server, Write has a write concurrency guarantee. See Open for
// additional requirements regarding lazy path resolution.
Write(buf []byte, off uint64) (uint64, error)
// Read reads at offset off into buf from the backing file via this open FD.
// Read attempts to read len(buf) bytes and returns the number of bytes read.
//
// On the server, Read has a read concurrency guarantee. See Open for
// additional requirements regarding lazy path resolution.
Read(buf []byte, off uint64) (uint64, error)
// Allocate allows the caller to directly manipulate the allocated disk space
// for the file. See fallocate(2) for more details.
//
// On the server, Allocate has a write concurrency guarantee.
Allocate(mode, off, length uint64) error
// Flush can be used to clean up the file state. Behavior is
// implementation-specific.
//
// On the server, Flush has a read concurrency guarantee.
Flush() error
// Getdent64 fetches directory entries for this directory and calls
// recordDirent for each dirent read. If seek0 is true, then the directory FD
// is seeked to 0 and iteration starts from the beginning.
//
// On the server, Getdent64 has a read concurrency guarantee.
Getdent64(count uint32, seek0 bool, recordDirent func(Dirent64)) error
// Renamed is called to notify the FD implementation that the file has been
// renamed. FD implementation may update its state accordingly.
//
// On the server, Renamed has a global concurrency guarantee.
Renamed()
}
// BoundSocketFDImpl represents a socket on the host filesystem that has been
// created by the sandboxed application via Bind.
type BoundSocketFDImpl interface {
// FD returns a pointer to the embedded BoundSocketFD.
FD() *BoundSocketFD
// Listen marks the socket as accepting incoming connections.
//
// On the server, Listen has a read concurrency guarantee.
Listen(backlog int32) error
// Accept takes the first pending connection and creates a new socket
// for it. The new socket FD is returned along with the peer address of
// the connecting socket (which may be empty string).
//
// On the server, Accept has a read concurrency guarantee.
Accept() (int, string, error)
// Close should clean up resources used by the bound socket FD
// implementation.
//
// Close is called after all references on the FD have been dropped and its
// FDID has been released.
//
// On the server, Close has no concurrency guarantee.
Close()
}
|