diff mbox series

[bpf-next,v5,4/8] libbpf: Create a bpf_link in bpf_map__attach_struct_ops().

Message ID 20230308005050.255859-5-kuifeng@meta.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Transit between BPF TCP congestion controls. | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-11 success Logs for test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for test_progs_no_alu32_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-31 success Logs for test_progs_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-36 success Logs for test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-39 success Logs for test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-40 success Logs for test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-5 success Logs for build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-7 success Logs for llvm-toolchain
bpf/vmtest-bpf-next-VM_Test-8 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-2 success Logs for build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-3 success Logs for build for aarch64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-4 success Logs for build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-37 success Logs for test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-38 success Logs for test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-9 success Logs for test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for test_maps on aarch64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-12 success Logs for test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-14 success Logs for test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for test_progs on aarch64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-17 success Logs for test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-19 success Logs for test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for test_progs_no_alu32 on aarch64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-22 success Logs for test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-24 success Logs for test_progs_no_alu32_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for test_progs_no_alu32_parallel on aarch64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-27 success Logs for test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-28 success Logs for test_progs_no_alu32_parallel on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-29 success Logs for test_progs_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-30 success Logs for test_progs_parallel on aarch64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-33 success Logs for test_progs_parallel on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-34 success Logs for test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-35 success Logs for test_verifier on aarch64 with llvm-17
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 20 this patch: 20
netdev/cc_maintainers warning 6 maintainers not CCed: haoluo@google.com yhs@fb.com daniel@iogearbox.net john.fastabend@gmail.com kpsingh@kernel.org jolsa@kernel.org
netdev/build_clang success Errors and warnings before: 18 this patch: 18
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 20 this patch: 20
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 133 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Kui-Feng Lee March 8, 2023, 12:50 a.m. UTC
bpf_map__attach_struct_ops() was creating a dummy bpf_link as a
placeholder, but now it is constructing an authentic one by calling
bpf_link_create() if the map has the BPF_F_LINK flag.

You can flag a struct_ops map with BPF_F_LINK by calling
bpf_map__set_map_flags().

Signed-off-by: Kui-Feng Lee <kuifeng@meta.com>
---
 tools/lib/bpf/libbpf.c | 84 +++++++++++++++++++++++++++++++-----------
 1 file changed, 62 insertions(+), 22 deletions(-)

Comments

Martin KaFai Lau March 8, 2023, 9:42 p.m. UTC | #1
On 3/7/23 4:50 PM, Kui-Feng Lee wrote:
> @@ -11566,22 +11591,34 @@ struct bpf_link *bpf_program__attach(const struct bpf_program *prog)
>   	return link;
>   }
>   
> +struct bpf_link_struct_ops {
> +	struct bpf_link link;
> +	int map_fd;
> +};
> +
>   static int bpf_link__detach_struct_ops(struct bpf_link *link)
>   {
> +	struct bpf_link_struct_ops *st_link;
>   	__u32 zero = 0;
>   
> -	if (bpf_map_delete_elem(link->fd, &zero))
> -		return -errno;
> +	st_link = container_of(link, struct bpf_link_struct_ops, link);
>   
> -	return 0;
> +	if (st_link->map_fd < 0) {

map_fd < 0 should always be true?

> +		/* Fake bpf_link */
> +		if (bpf_map_delete_elem(link->fd, &zero))
> +			return -errno;
> +		return 0;
> +	}
> +
> +	/* Doesn't support detaching. */
> +	return -EOPNOTSUPP;
>   }
>   
>   struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
>   {
> -	struct bpf_struct_ops *st_ops;
> -	struct bpf_link *link;
> -	__u32 i, zero = 0;
> -	int err;
> +	struct bpf_link_struct_ops *link;
> +	__u32 zero = 0;
> +	int err, fd;
>   
>   	if (!bpf_map__is_struct_ops(map) || map->fd == -1)
>   		return libbpf_err_ptr(-EINVAL);
> @@ -11590,31 +11627,34 @@ struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
>   	if (!link)
>   		return libbpf_err_ptr(-EINVAL);
>   
> -	st_ops = map->st_ops;
> -	for (i = 0; i < btf_vlen(st_ops->type); i++) {
> -		struct bpf_program *prog = st_ops->progs[i];
> -		void *kern_data;
> -		int prog_fd;
> +	/* kern_vdata should be prepared during the loading phase. */
> +	err = bpf_map_update_elem(map->fd, &zero, map->st_ops->kern_vdata, 0);
> +	if (err) {
> +		err = -errno;
> +		free(link);
> +		return libbpf_err_ptr(err);
> +	}
>   
> -		if (!prog)
> -			continue;
>   
> -		prog_fd = bpf_program__fd(prog);
> -		kern_data = st_ops->kern_vdata + st_ops->kern_func_off[i];
> -		*(unsigned long *)kern_data = prog_fd;
> +	if (!(map->def.map_flags & BPF_F_LINK)) {
> +		/* Fake bpf_link */
> +		link->link.fd = map->fd;
> +		link->map_fd = -1;
> +		link->link.detach = bpf_link__detach_struct_ops;
> +		return &link->link;
>   	}
>   
> -	err = bpf_map_update_elem(map->fd, &zero, st_ops->kern_vdata, 0);
> -	if (err) {
> +	fd = bpf_link_create(map->fd, -1, BPF_STRUCT_OPS, NULL);
> +	if (fd < 0) {
>   		err = -errno;
>   		free(link);
>   		return libbpf_err_ptr(err);
>   	}
>   
> -	link->detach = bpf_link__detach_struct_ops;
> -	link->fd = map->fd;
> +	link->link.fd = fd;
> +	link->map_fd = map->fd;

Does it need to set link->link.detach?

>   
> -	return link;
> +	return &link->link;
>   }
>   
>   typedef enum bpf_perf_event_ret (*bpf_perf_event_print_t)(struct perf_event_header *hdr,
Kui-Feng Lee March 9, 2023, 12:22 a.m. UTC | #2
On 3/8/23 13:42, Martin KaFai Lau wrote:
> On 3/7/23 4:50 PM, Kui-Feng Lee wrote:
>> @@ -11566,22 +11591,34 @@ struct bpf_link *bpf_program__attach(const 
>> struct bpf_program *prog)
>>       return link;
>>   }
>> +struct bpf_link_struct_ops {
>> +    struct bpf_link link;
>> +    int map_fd;
>> +};
>> +
>>   static int bpf_link__detach_struct_ops(struct bpf_link *link)
>>   {
>> +    struct bpf_link_struct_ops *st_link;
>>       __u32 zero = 0;
>> -    if (bpf_map_delete_elem(link->fd, &zero))
>> -        return -errno;
>> +    st_link = container_of(link, struct bpf_link_struct_ops, link);
>> -    return 0;
>> +    if (st_link->map_fd < 0) {
> 
> map_fd < 0 should always be true?

If the user pass a wrong link, it can fail.
I check it here explicitly even the kernel returns
an error for deleting an element of a struct_ops w/ link.

> 
>> +        /* Fake bpf_link */
>> +        if (bpf_map_delete_elem(link->fd, &zero))
>> +            return -errno;
>> +        return 0;
>> +    }
>> +
>> +    /* Doesn't support detaching. */
>> +    return -EOPNOTSUPP;
>>   }
>>   struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
>>   {
>> -    struct bpf_struct_ops *st_ops;
>> -    struct bpf_link *link;
>> -    __u32 i, zero = 0;
>> -    int err;
>> +    struct bpf_link_struct_ops *link;
>> +    __u32 zero = 0;
>> +    int err, fd;
>>       if (!bpf_map__is_struct_ops(map) || map->fd == -1)
>>           return libbpf_err_ptr(-EINVAL);
>> @@ -11590,31 +11627,34 @@ struct bpf_link 
>> *bpf_map__attach_struct_ops(const struct bpf_map *map)
>>       if (!link)
>>           return libbpf_err_ptr(-EINVAL);
>> -    st_ops = map->st_ops;
>> -    for (i = 0; i < btf_vlen(st_ops->type); i++) {
>> -        struct bpf_program *prog = st_ops->progs[i];
>> -        void *kern_data;
>> -        int prog_fd;
>> +    /* kern_vdata should be prepared during the loading phase. */
>> +    err = bpf_map_update_elem(map->fd, &zero, 
>> map->st_ops->kern_vdata, 0);
>> +    if (err) {
>> +        err = -errno;
>> +        free(link);
>> +        return libbpf_err_ptr(err);
>> +    }
>> -        if (!prog)
>> -            continue;
>> -        prog_fd = bpf_program__fd(prog);
>> -        kern_data = st_ops->kern_vdata + st_ops->kern_func_off[i];
>> -        *(unsigned long *)kern_data = prog_fd;
>> +    if (!(map->def.map_flags & BPF_F_LINK)) {
>> +        /* Fake bpf_link */
>> +        link->link.fd = map->fd;
>> +        link->map_fd = -1;
>> +        link->link.detach = bpf_link__detach_struct_ops;
>> +        return &link->link;
>>       }
>> -    err = bpf_map_update_elem(map->fd, &zero, st_ops->kern_vdata, 0);
>> -    if (err) {
>> +    fd = bpf_link_create(map->fd, -1, BPF_STRUCT_OPS, NULL);
>> +    if (fd < 0) {
>>           err = -errno;
>>           free(link);
>>           return libbpf_err_ptr(err);
>>       }
>> -    link->detach = bpf_link__detach_struct_ops;
>> -    link->fd = map->fd;
>> +    link->link.fd = fd;
>> +    link->map_fd = map->fd;
> 
> Does it need to set link->link.detach?

Yes, I have made some changes to this part. The new code will set
link->link.detach for BPF_F_LINK as well to cleanup fd.

> 
>> -    return link;
>> +    return &link->link;
>>   }
>>   typedef enum bpf_perf_event_ret (*bpf_perf_event_print_t)(struct 
>> perf_event_header *hdr,
>
Martin KaFai Lau March 9, 2023, 5:09 p.m. UTC | #3
On 3/8/23 4:22 PM, Kui-Feng Lee wrote:
> 
> 
> On 3/8/23 13:42, Martin KaFai Lau wrote:
>> On 3/7/23 4:50 PM, Kui-Feng Lee wrote:
>>> @@ -11566,22 +11591,34 @@ struct bpf_link *bpf_program__attach(const struct 
>>> bpf_program *prog)
>>>       return link;
>>>   }
>>> +struct bpf_link_struct_ops {
>>> +    struct bpf_link link;
>>> +    int map_fd;
>>> +};
>>> +
>>>   static int bpf_link__detach_struct_ops(struct bpf_link *link)
>>>   {
>>> +    struct bpf_link_struct_ops *st_link;
>>>       __u32 zero = 0;
>>> -    if (bpf_map_delete_elem(link->fd, &zero))
>>> -        return -errno;
>>> +    st_link = container_of(link, struct bpf_link_struct_ops, link);
>>> -    return 0;
>>> +    if (st_link->map_fd < 0) {
>>
>> map_fd < 0 should always be true?
> 
> If the user pass a wrong link, it can fail.

I may have missed something. How can user directly pass a link to this static 
function?

> I check it here explicitly even the kernel returns
> an error for deleting an element of a struct_ops w/ link.
Yep, the kernel should have stopped the delete if the user somehow corrupted the 
map_fd to -1.

> 
>>
>>> +        /* Fake bpf_link */
>>> +        if (bpf_map_delete_elem(link->fd, &zero))
>>> +            return -errno;
>>> +        return 0;
>>> +    }
>>> +
>>> +    /* Doesn't support detaching. */
>>> +    return -EOPNOTSUPP;
Kui-Feng Lee March 9, 2023, 6:16 p.m. UTC | #4
On 3/9/23 09:09, Martin KaFai Lau wrote:
> On 3/8/23 4:22 PM, Kui-Feng Lee wrote:
>>
>>
>> On 3/8/23 13:42, Martin KaFai Lau wrote:
>>> On 3/7/23 4:50 PM, Kui-Feng Lee wrote:
>>>> @@ -11566,22 +11591,34 @@ struct bpf_link *bpf_program__attach(const 
>>>> struct bpf_program *prog)
>>>>       return link;
>>>>   }
>>>> +struct bpf_link_struct_ops {
>>>> +    struct bpf_link link;
>>>> +    int map_fd;
>>>> +};
>>>> +
>>>>   static int bpf_link__detach_struct_ops(struct bpf_link *link)
>>>>   {
>>>> +    struct bpf_link_struct_ops *st_link;
>>>>       __u32 zero = 0;
>>>> -    if (bpf_map_delete_elem(link->fd, &zero))
>>>> -        return -errno;
>>>> +    st_link = container_of(link, struct bpf_link_struct_ops, link);
>>>> -    return 0;
>>>> +    if (st_link->map_fd < 0) {
>>>
>>> map_fd < 0 should always be true?
>>
>> If the user pass a wrong link, it can fail.
> 
> I may have missed something. How can user directly pass a link to this 
> static function?

Ouch! You are right. This check is not necessary. I mixed it with the 
old detach feature.


> 
>> I check it here explicitly even the kernel returns
>> an error for deleting an element of a struct_ops w/ link.
> Yep, the kernel should have stopped the delete if the user somehow 
> corrupted the map_fd to -1.
> 
>>
>>>
>>>> +        /* Fake bpf_link */
>>>> +        if (bpf_map_delete_elem(link->fd, &zero))
>>>> +            return -errno;
>>>> +        return 0;
>>>> +    }
>>>> +
>>>> +    /* Doesn't support detaching. */
>>>> +    return -EOPNOTSUPP;
>
Kui-Feng Lee March 9, 2023, 6:19 p.m. UTC | #5
On 3/9/23 10:16, Kui-Feng Lee wrote:
> 
> 
> On 3/9/23 09:09, Martin KaFai Lau wrote:
>> On 3/8/23 4:22 PM, Kui-Feng Lee wrote:
>>>
>>>
>>> On 3/8/23 13:42, Martin KaFai Lau wrote:
>>>> On 3/7/23 4:50 PM, Kui-Feng Lee wrote:
>>>>> @@ -11566,22 +11591,34 @@ struct bpf_link 
>>>>> *bpf_program__attach(const struct bpf_program *prog)
>>>>>       return link;
>>>>>   }
>>>>> +struct bpf_link_struct_ops {
>>>>> +    struct bpf_link link;
>>>>> +    int map_fd;
>>>>> +};
>>>>> +
>>>>>   static int bpf_link__detach_struct_ops(struct bpf_link *link)
>>>>>   {
>>>>> +    struct bpf_link_struct_ops *st_link;
>>>>>       __u32 zero = 0;
>>>>> -    if (bpf_map_delete_elem(link->fd, &zero))
>>>>> -        return -errno;
>>>>> +    st_link = container_of(link, struct bpf_link_struct_ops, link);
>>>>> -    return 0;
>>>>> +    if (st_link->map_fd < 0) {
>>>>
>>>> map_fd < 0 should always be true?
>>>
>>> If the user pass a wrong link, it can fail.
>>
>> I may have missed something. How can user directly pass a link to this 
>> static function?
> 
> Ouch! You are right. This check is not necessary. I mixed it with the 
> old detach feature.

By the way, I will keep this test here since this function will handle
the case w/o a link as well.

> 
> 
>>
>>> I check it here explicitly even the kernel returns
>>> an error for deleting an element of a struct_ops w/ link.
>> Yep, the kernel should have stopped the delete if the user somehow 
>> corrupted the map_fd to -1.
>>
>>>
>>>>
>>>>> +        /* Fake bpf_link */
>>>>> +        if (bpf_map_delete_elem(link->fd, &zero))
>>>>> +            return -errno;
>>>>> +        return 0;
>>>>> +    }
>>>>> +
>>>>> +    /* Doesn't support detaching. */
>>>>> +    return -EOPNOTSUPP;
>>
diff mbox series

Patch

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index a557718401e4..f70b55c0f40e 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -116,6 +116,7 @@  static const char * const attach_type_name[] = {
 	[BPF_SK_REUSEPORT_SELECT_OR_MIGRATE]	= "sk_reuseport_select_or_migrate",
 	[BPF_PERF_EVENT]		= "perf_event",
 	[BPF_TRACE_KPROBE_MULTI]	= "trace_kprobe_multi",
+	[BPF_STRUCT_OPS]		= "struct_ops",
 };
 
 static const char * const link_type_name[] = {
@@ -7677,6 +7678,26 @@  static int bpf_object__resolve_externs(struct bpf_object *obj,
 	return 0;
 }
 
+static void bpf_map_prepare_vdata(const struct bpf_map *map)
+{
+	struct bpf_struct_ops *st_ops;
+	__u32 i;
+
+	st_ops = map->st_ops;
+	for (i = 0; i < btf_vlen(st_ops->type); i++) {
+		struct bpf_program *prog = st_ops->progs[i];
+		void *kern_data;
+		int prog_fd;
+
+		if (!prog)
+			continue;
+
+		prog_fd = bpf_program__fd(prog);
+		kern_data = st_ops->kern_vdata + st_ops->kern_func_off[i];
+		*(unsigned long *)kern_data = prog_fd;
+	}
+}
+
 static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const char *target_btf_path)
 {
 	int err, i;
@@ -7728,6 +7749,10 @@  static int bpf_object_load(struct bpf_object *obj, int extra_log_level, const ch
 	btf__free(obj->btf_vmlinux);
 	obj->btf_vmlinux = NULL;
 
+	for (i = 0; i < obj->nr_maps; i++)
+		if (bpf_map__is_struct_ops(&obj->maps[i]))
+			bpf_map_prepare_vdata(&obj->maps[i]);
+
 	obj->loaded = true; /* doesn't matter if successfully or not */
 
 	if (err)
@@ -11566,22 +11591,34 @@  struct bpf_link *bpf_program__attach(const struct bpf_program *prog)
 	return link;
 }
 
+struct bpf_link_struct_ops {
+	struct bpf_link link;
+	int map_fd;
+};
+
 static int bpf_link__detach_struct_ops(struct bpf_link *link)
 {
+	struct bpf_link_struct_ops *st_link;
 	__u32 zero = 0;
 
-	if (bpf_map_delete_elem(link->fd, &zero))
-		return -errno;
+	st_link = container_of(link, struct bpf_link_struct_ops, link);
 
-	return 0;
+	if (st_link->map_fd < 0) {
+		/* Fake bpf_link */
+		if (bpf_map_delete_elem(link->fd, &zero))
+			return -errno;
+		return 0;
+	}
+
+	/* Doesn't support detaching. */
+	return -EOPNOTSUPP;
 }
 
 struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
 {
-	struct bpf_struct_ops *st_ops;
-	struct bpf_link *link;
-	__u32 i, zero = 0;
-	int err;
+	struct bpf_link_struct_ops *link;
+	__u32 zero = 0;
+	int err, fd;
 
 	if (!bpf_map__is_struct_ops(map) || map->fd == -1)
 		return libbpf_err_ptr(-EINVAL);
@@ -11590,31 +11627,34 @@  struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
 	if (!link)
 		return libbpf_err_ptr(-EINVAL);
 
-	st_ops = map->st_ops;
-	for (i = 0; i < btf_vlen(st_ops->type); i++) {
-		struct bpf_program *prog = st_ops->progs[i];
-		void *kern_data;
-		int prog_fd;
+	/* kern_vdata should be prepared during the loading phase. */
+	err = bpf_map_update_elem(map->fd, &zero, map->st_ops->kern_vdata, 0);
+	if (err) {
+		err = -errno;
+		free(link);
+		return libbpf_err_ptr(err);
+	}
 
-		if (!prog)
-			continue;
 
-		prog_fd = bpf_program__fd(prog);
-		kern_data = st_ops->kern_vdata + st_ops->kern_func_off[i];
-		*(unsigned long *)kern_data = prog_fd;
+	if (!(map->def.map_flags & BPF_F_LINK)) {
+		/* Fake bpf_link */
+		link->link.fd = map->fd;
+		link->map_fd = -1;
+		link->link.detach = bpf_link__detach_struct_ops;
+		return &link->link;
 	}
 
-	err = bpf_map_update_elem(map->fd, &zero, st_ops->kern_vdata, 0);
-	if (err) {
+	fd = bpf_link_create(map->fd, -1, BPF_STRUCT_OPS, NULL);
+	if (fd < 0) {
 		err = -errno;
 		free(link);
 		return libbpf_err_ptr(err);
 	}
 
-	link->detach = bpf_link__detach_struct_ops;
-	link->fd = map->fd;
+	link->link.fd = fd;
+	link->map_fd = map->fd;
 
-	return link;
+	return &link->link;
 }
 
 typedef enum bpf_perf_event_ret (*bpf_perf_event_print_t)(struct perf_event_header *hdr,