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 }