2020-05-04 20:46:48 +00:00
|
|
|
package ebpf
|
|
|
|
|
|
|
|
import (
|
2020-08-10 17:43:49 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2020-05-04 20:46:48 +00:00
|
|
|
"unsafe"
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
"github.com/cilium/ebpf/internal"
|
|
|
|
"github.com/cilium/ebpf/internal/btf"
|
2020-05-04 20:46:48 +00:00
|
|
|
"github.com/cilium/ebpf/internal/unix"
|
|
|
|
)
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
// Generic errors returned by BPF syscalls.
|
|
|
|
var (
|
|
|
|
ErrNotExist = errors.New("requested object does not exist")
|
|
|
|
)
|
2020-05-04 20:46:48 +00:00
|
|
|
|
|
|
|
// bpfObjName is a null-terminated string made up of
|
|
|
|
// 'A-Za-z0-9_' characters.
|
|
|
|
type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte
|
|
|
|
|
|
|
|
// newBPFObjName truncates the result if it is too long.
|
2020-08-10 17:43:49 +00:00
|
|
|
func newBPFObjName(name string) bpfObjName {
|
2020-05-04 20:46:48 +00:00
|
|
|
var result bpfObjName
|
|
|
|
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
|
2020-08-10 17:43:49 +00:00
|
|
|
return result
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func invalidBPFObjNameChar(char rune) bool {
|
2020-08-10 17:43:49 +00:00
|
|
|
dotAllowed := objNameAllowsDot() == nil
|
|
|
|
|
2020-05-04 20:46:48 +00:00
|
|
|
switch {
|
|
|
|
case char >= 'A' && char <= 'Z':
|
|
|
|
fallthrough
|
|
|
|
case char >= 'a' && char <= 'z':
|
|
|
|
fallthrough
|
|
|
|
case char >= '0' && char <= '9':
|
|
|
|
fallthrough
|
2020-08-10 17:43:49 +00:00
|
|
|
case dotAllowed && char == '.':
|
|
|
|
fallthrough
|
2020-05-04 20:46:48 +00:00
|
|
|
case char == '_':
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type bpfMapCreateAttr struct {
|
2020-08-10 17:43:49 +00:00
|
|
|
mapType MapType
|
|
|
|
keySize uint32
|
|
|
|
valueSize uint32
|
|
|
|
maxEntries uint32
|
|
|
|
flags uint32
|
|
|
|
innerMapFd uint32 // since 4.12 56f668dfe00d
|
|
|
|
numaNode uint32 // since 4.14 96eabe7a40aa
|
|
|
|
mapName bpfObjName // since 4.15 ad5b177bd73f
|
|
|
|
mapIfIndex uint32
|
|
|
|
btfFd uint32
|
|
|
|
btfKeyTypeID btf.TypeID
|
|
|
|
btfValueTypeID btf.TypeID
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type bpfMapOpAttr struct {
|
|
|
|
mapFd uint32
|
|
|
|
padding uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
key internal.Pointer
|
|
|
|
value internal.Pointer
|
2020-05-04 20:46:48 +00:00
|
|
|
flags uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
type bpfMapInfo struct {
|
|
|
|
mapType uint32
|
|
|
|
id uint32
|
|
|
|
keySize uint32
|
|
|
|
valueSize uint32
|
|
|
|
maxEntries uint32
|
|
|
|
flags uint32
|
|
|
|
mapName bpfObjName // since 4.15 ad5b177bd73f
|
|
|
|
}
|
|
|
|
|
|
|
|
type bpfProgLoadAttr struct {
|
|
|
|
progType ProgramType
|
|
|
|
insCount uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
instructions internal.Pointer
|
|
|
|
license internal.Pointer
|
2020-05-04 20:46:48 +00:00
|
|
|
logLevel uint32
|
|
|
|
logSize uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
logBuf internal.Pointer
|
2020-05-04 20:46:48 +00:00
|
|
|
kernelVersion uint32 // since 4.1 2541517c32be
|
|
|
|
progFlags uint32 // since 4.11 e07b98d9bffe
|
|
|
|
progName bpfObjName // since 4.15 067cae47771c
|
|
|
|
progIfIndex uint32 // since 4.15 1f6f4cb7ba21
|
|
|
|
expectedAttachType AttachType // since 4.17 5e43f899b03a
|
2020-08-10 17:43:49 +00:00
|
|
|
progBTFFd uint32
|
|
|
|
funcInfoRecSize uint32
|
|
|
|
funcInfo internal.Pointer
|
|
|
|
funcInfoCnt uint32
|
|
|
|
lineInfoRecSize uint32
|
|
|
|
lineInfo internal.Pointer
|
|
|
|
lineInfoCnt uint32
|
|
|
|
attachBTFID btf.TypeID
|
|
|
|
attachProgFd uint32
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type bpfProgInfo struct {
|
|
|
|
progType uint32
|
|
|
|
id uint32
|
|
|
|
tag [unix.BPF_TAG_SIZE]byte
|
|
|
|
jitedLen uint32
|
|
|
|
xlatedLen uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
jited internal.Pointer
|
|
|
|
xlated internal.Pointer
|
2020-05-04 20:46:48 +00:00
|
|
|
loadTime uint64 // since 4.15 cb4d2b3f03d8
|
|
|
|
createdByUID uint32
|
|
|
|
nrMapIDs uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
mapIds internal.Pointer
|
2020-05-04 20:46:48 +00:00
|
|
|
name bpfObjName
|
|
|
|
}
|
|
|
|
|
|
|
|
type bpfProgTestRunAttr struct {
|
|
|
|
fd uint32
|
|
|
|
retval uint32
|
|
|
|
dataSizeIn uint32
|
|
|
|
dataSizeOut uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
dataIn internal.Pointer
|
|
|
|
dataOut internal.Pointer
|
2020-05-04 20:46:48 +00:00
|
|
|
repeat uint32
|
|
|
|
duration uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
type bpfObjGetInfoByFDAttr struct {
|
|
|
|
fd uint32
|
|
|
|
infoLen uint32
|
2020-08-10 17:43:49 +00:00
|
|
|
info internal.Pointer // May be either bpfMapInfo or bpfProgInfo
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type bpfGetFDByIDAttr struct {
|
|
|
|
id uint32
|
|
|
|
next uint32
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
type bpfMapFreezeAttr struct {
|
|
|
|
mapFd uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
type bpfObjGetNextIDAttr struct {
|
|
|
|
startID uint32
|
|
|
|
nextID uint32
|
|
|
|
openFlags uint32
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) {
|
2020-05-04 20:46:48 +00:00
|
|
|
for {
|
2020-08-10 17:43:49 +00:00
|
|
|
fd, err := internal.BPF(internal.BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
2020-05-04 20:46:48 +00:00
|
|
|
// As of ~4.20 the verifier can be interrupted by a signal,
|
|
|
|
// and returns EAGAIN in that case.
|
|
|
|
if err == unix.EAGAIN {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
return internal.NewFD(uint32(fd)), nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfProgTestRun(attr *bpfProgTestRunAttr) error {
|
|
|
|
_, err := internal.BPF(internal.BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
2020-05-04 20:46:48 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
|
|
|
|
fd, err := internal.BPF(internal.BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
|
|
|
if errors.Is(err, os.ErrPermission) {
|
|
|
|
return nil, errors.New("permission denied or insufficient rlimit to lock memory for map")
|
|
|
|
}
|
|
|
|
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
return internal.NewFD(uint32(fd)), nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() (bool, error) {
|
|
|
|
inner, err := bpfMapCreate(&bpfMapCreateAttr{
|
|
|
|
mapType: Array,
|
|
|
|
keySize: 4,
|
|
|
|
valueSize: 4,
|
|
|
|
maxEntries: 1,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
defer inner.Close()
|
|
|
|
|
|
|
|
innerFd, _ := inner.Value()
|
|
|
|
nested, err := bpfMapCreate(&bpfMapCreateAttr{
|
|
|
|
mapType: ArrayOfMaps,
|
|
|
|
keySize: 4,
|
|
|
|
valueSize: 4,
|
|
|
|
maxEntries: 1,
|
|
|
|
innerMapFd: innerFd,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = nested.Close()
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() (bool, error) {
|
|
|
|
// This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since
|
|
|
|
// BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check.
|
|
|
|
m, err := bpfMapCreate(&bpfMapCreateAttr{
|
|
|
|
mapType: Array,
|
|
|
|
keySize: 4,
|
|
|
|
valueSize: 4,
|
|
|
|
maxEntries: 1,
|
|
|
|
flags: unix.BPF_F_RDONLY_PROG,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
_ = m.Close()
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
|
|
|
|
fd, err := m.Value()
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := bpfMapOpAttr{
|
|
|
|
mapFd: fd,
|
|
|
|
key: key,
|
|
|
|
value: valueOut,
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
_, err = internal.BPF(internal.BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return wrapMapError(err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error {
|
|
|
|
fd, err := m.Value()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := bpfMapOpAttr{
|
|
|
|
mapFd: fd,
|
|
|
|
key: key,
|
|
|
|
value: valueOut,
|
|
|
|
}
|
|
|
|
_, err = internal.BPF(internal.BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return wrapMapError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint64) error {
|
|
|
|
fd, err := m.Value()
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := bpfMapOpAttr{
|
|
|
|
mapFd: fd,
|
|
|
|
key: key,
|
|
|
|
value: valueOut,
|
|
|
|
flags: flags,
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
_, err = internal.BPF(internal.BPF_MAP_UPDATE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return wrapMapError(err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
|
|
|
|
fd, err := m.Value()
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := bpfMapOpAttr{
|
|
|
|
mapFd: fd,
|
|
|
|
key: key,
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
_, err = internal.BPF(internal.BPF_MAP_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return wrapMapError(err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
|
|
|
|
fd, err := m.Value()
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := bpfMapOpAttr{
|
|
|
|
mapFd: fd,
|
|
|
|
key: key,
|
|
|
|
value: nextKeyOut,
|
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
_, err = internal.BPF(internal.BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return wrapMapError(err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func objGetNextID(cmd internal.BPFCmd, start uint32) (uint32, error) {
|
|
|
|
attr := bpfObjGetNextIDAttr{
|
|
|
|
startID: start,
|
|
|
|
}
|
|
|
|
_, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return attr.nextID, wrapObjError(err)
|
|
|
|
}
|
2020-05-04 20:46:48 +00:00
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func wrapObjError(err error) error {
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
if errors.Is(err, unix.ENOENT) {
|
|
|
|
return fmt.Errorf("%w", ErrNotExist)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
return errors.New(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
func wrapMapError(err error) error {
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if errors.Is(err, unix.ENOENT) {
|
|
|
|
return ErrKeyNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
if errors.Is(err, unix.EEXIST) {
|
|
|
|
return ErrKeyExist
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
return errors.New(err.Error())
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfMapFreeze(m *internal.FD) error {
|
|
|
|
fd, err := m.Value()
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
|
|
|
|
attr := bpfMapFreezeAttr{
|
|
|
|
mapFd: fd,
|
|
|
|
}
|
|
|
|
_, err = internal.BPF(internal.BPF_MAP_FREEZE, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
|
|
|
|
value, err := fd.Value()
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// available from 4.13
|
|
|
|
attr := bpfObjGetInfoByFDAttr{
|
|
|
|
fd: value,
|
|
|
|
infoLen: uint32(size),
|
2020-08-10 17:43:49 +00:00
|
|
|
info: internal.NewPointer(info),
|
|
|
|
}
|
|
|
|
_, err = internal.BPF(internal.BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("fd %d: %w", fd, err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
return nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
|
2020-05-04 20:46:48 +00:00
|
|
|
var info bpfProgInfo
|
2020-08-10 17:43:49 +00:00
|
|
|
if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
|
|
|
|
return nil, fmt.Errorf("can't get program info: %w", err)
|
|
|
|
}
|
|
|
|
return &info, nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
|
2020-05-04 20:46:48 +00:00
|
|
|
var info bpfMapInfo
|
|
|
|
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
|
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
return nil, fmt.Errorf("can't get map info: %w", err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
return &info, nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
var haveObjName = internal.FeatureTest("object names", "4.15", func() (bool, error) {
|
|
|
|
attr := bpfMapCreateAttr{
|
|
|
|
mapType: Array,
|
|
|
|
keySize: 4,
|
|
|
|
valueSize: 4,
|
|
|
|
maxEntries: 1,
|
|
|
|
mapName: newBPFObjName("feature_test"),
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
|
|
|
|
fd, err := bpfMapCreate(&attr)
|
2020-05-04 20:46:48 +00:00
|
|
|
if err != nil {
|
2020-08-10 17:43:49 +00:00
|
|
|
return false, nil
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
_ = fd.Close()
|
|
|
|
return true, nil
|
|
|
|
})
|
2020-05-04 20:46:48 +00:00
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() (bool, error) {
|
|
|
|
if err := haveObjName(); err != nil {
|
|
|
|
return false, err
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
attr := bpfMapCreateAttr{
|
|
|
|
mapType: Array,
|
|
|
|
keySize: 4,
|
|
|
|
valueSize: 4,
|
|
|
|
maxEntries: 1,
|
|
|
|
mapName: newBPFObjName(".test"),
|
|
|
|
}
|
|
|
|
|
|
|
|
fd, err := bpfMapCreate(&attr)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
2020-05-04 20:46:48 +00:00
|
|
|
|
2020-08-10 17:43:49 +00:00
|
|
|
_ = fd.Close()
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
|
|
|
|
func bpfObjGetFDByID(cmd internal.BPFCmd, id uint32) (*internal.FD, error) {
|
|
|
|
attr := bpfGetFDByIDAttr{
|
|
|
|
id: id,
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|
2020-08-10 17:43:49 +00:00
|
|
|
ptr, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
|
|
|
return internal.NewFD(uint32(ptr)), wrapObjError(err)
|
2020-05-04 20:46:48 +00:00
|
|
|
}
|