github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/sys/fd.go (about)

     1  package sys
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"os"
     7  	"runtime"
     8  	"strconv"
     9  
    10  	"github.com/cilium/ebpf/internal/unix"
    11  )
    12  
    13  var ErrClosedFd = unix.EBADF
    14  
    15  type FD struct {
    16  	raw int
    17  }
    18  
    19  func newFD(value int) *FD {
    20  	if onLeakFD != nil {
    21  		// Attempt to store the caller's stack for the given fd value.
    22  		// Panic if fds contains an existing stack for the fd.
    23  		old, exist := fds.LoadOrStore(value, callersFrames())
    24  		if exist {
    25  			f := old.(*runtime.Frames)
    26  			panic(fmt.Sprintf("found existing stack for fd %d:\n%s", value, FormatFrames(f)))
    27  		}
    28  	}
    29  
    30  	fd := &FD{value}
    31  	runtime.SetFinalizer(fd, (*FD).finalize)
    32  	return fd
    33  }
    34  
    35  // finalize is set as the FD's runtime finalizer and
    36  // sends a leak trace before calling FD.Close().
    37  func (fd *FD) finalize() {
    38  	if fd.raw < 0 {
    39  		return
    40  	}
    41  
    42  	// Invoke the fd leak callback. Calls LoadAndDelete to guarantee the callback
    43  	// is invoked at most once for one sys.FD allocation, runtime.Frames can only
    44  	// be unwound once.
    45  	f, ok := fds.LoadAndDelete(fd.Int())
    46  	if ok && onLeakFD != nil {
    47  		onLeakFD(f.(*runtime.Frames))
    48  	}
    49  
    50  	_ = fd.Close()
    51  }
    52  
    53  // NewFD wraps a raw fd with a finalizer.
    54  //
    55  // You must not use the raw fd after calling this function, since the underlying
    56  // file descriptor number may change. This is because the BPF UAPI assumes that
    57  // zero is not a valid fd value.
    58  func NewFD(value int) (*FD, error) {
    59  	if value < 0 {
    60  		return nil, fmt.Errorf("invalid fd %d", value)
    61  	}
    62  
    63  	fd := newFD(value)
    64  	if value != 0 {
    65  		return fd, nil
    66  	}
    67  
    68  	dup, err := fd.Dup()
    69  	_ = fd.Close()
    70  	return dup, err
    71  }
    72  
    73  func (fd *FD) String() string {
    74  	return strconv.FormatInt(int64(fd.raw), 10)
    75  }
    76  
    77  func (fd *FD) Int() int {
    78  	return fd.raw
    79  }
    80  
    81  func (fd *FD) Uint() uint32 {
    82  	if fd.raw < 0 || int64(fd.raw) > math.MaxUint32 {
    83  		// Best effort: this is the number most likely to be an invalid file
    84  		// descriptor. It is equal to -1 (on two's complement arches).
    85  		return math.MaxUint32
    86  	}
    87  	return uint32(fd.raw)
    88  }
    89  
    90  func (fd *FD) Close() error {
    91  	if fd.raw < 0 {
    92  		return nil
    93  	}
    94  
    95  	return unix.Close(fd.disown())
    96  }
    97  
    98  func (fd *FD) disown() int {
    99  	value := int(fd.raw)
   100  	fds.Delete(int(value))
   101  	fd.raw = -1
   102  
   103  	runtime.SetFinalizer(fd, nil)
   104  	return value
   105  }
   106  
   107  func (fd *FD) Dup() (*FD, error) {
   108  	if fd.raw < 0 {
   109  		return nil, ErrClosedFd
   110  	}
   111  
   112  	// Always require the fd to be larger than zero: the BPF API treats the value
   113  	// as "no argument provided".
   114  	dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 1)
   115  	if err != nil {
   116  		return nil, fmt.Errorf("can't dup fd: %v", err)
   117  	}
   118  
   119  	return newFD(dup), nil
   120  }
   121  
   122  // File takes ownership of FD and turns it into an [*os.File].
   123  //
   124  // You must not use the FD after the call returns.
   125  //
   126  // Returns nil if the FD is not valid.
   127  func (fd *FD) File(name string) *os.File {
   128  	if fd.raw < 0 {
   129  		return nil
   130  	}
   131  
   132  	return os.NewFile(uintptr(fd.disown()), name)
   133  }