1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2019 Facebook  */
3#include <stdio.h>
4#include <errno.h>
5#include <string.h>
6#include <unistd.h>
7
8#include <bpf/bpf.h>
9#include <bpf/libbpf.h>
10
11#include <bpf_util.h>
12#include <test_maps.h>
13
14static void map_batch_update(int map_fd, __u32 max_entries, int *keys,
15			     void *values, bool is_pcpu)
16{
17	typedef BPF_DECLARE_PERCPU(int, value);
18	value *v = NULL;
19	int i, j, err;
20	DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
21		.elem_flags = 0,
22		.flags = 0,
23	);
24
25	if (is_pcpu)
26		v = (value *)values;
27
28	for (i = 0; i < max_entries; i++) {
29		keys[i] = i + 1;
30		if (is_pcpu)
31			for (j = 0; j < bpf_num_possible_cpus(); j++)
32				bpf_percpu(v[i], j) = i + 2 + j;
33		else
34			((int *)values)[i] = i + 2;
35	}
36
37	err = bpf_map_update_batch(map_fd, keys, values, &max_entries, &opts);
38	CHECK(err, "bpf_map_update_batch()", "error:%s\n", strerror(errno));
39}
40
41static void map_batch_verify(int *visited, __u32 max_entries,
42			     int *keys, void *values, bool is_pcpu)
43{
44	typedef BPF_DECLARE_PERCPU(int, value);
45	value *v = NULL;
46	int i, j;
47
48	if (is_pcpu)
49		v = (value *)values;
50
51	memset(visited, 0, max_entries * sizeof(*visited));
52	for (i = 0; i < max_entries; i++) {
53
54		if (is_pcpu) {
55			for (j = 0; j < bpf_num_possible_cpus(); j++) {
56				CHECK(keys[i] + 1 + j != bpf_percpu(v[i], j),
57				      "key/value checking",
58				      "error: i %d j %d key %d value %d\n",
59				      i, j, keys[i], bpf_percpu(v[i],  j));
60			}
61		} else {
62			CHECK(keys[i] + 1 != ((int *)values)[i],
63			      "key/value checking",
64			      "error: i %d key %d value %d\n", i, keys[i],
65			      ((int *)values)[i]);
66		}
67
68		visited[i] = 1;
69
70	}
71	for (i = 0; i < max_entries; i++) {
72		CHECK(visited[i] != 1, "visited checking",
73		      "error: keys array at index %d missing\n", i);
74	}
75}
76
77void __test_map_lookup_and_delete_batch(bool is_pcpu)
78{
79	__u32 batch, count, total, total_success;
80	typedef BPF_DECLARE_PERCPU(int, value);
81	int map_fd, *keys, *visited, key;
82	const __u32 max_entries = 10;
83	value pcpu_values[max_entries];
84	int err, step, value_size;
85	bool nospace_err;
86	void *values;
87	DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
88		.elem_flags = 0,
89		.flags = 0,
90	);
91
92	map_fd = bpf_map_create(is_pcpu ? BPF_MAP_TYPE_PERCPU_HASH : BPF_MAP_TYPE_HASH,
93				"hash_map", sizeof(int), sizeof(int), max_entries, NULL);
94	CHECK(map_fd == -1,
95	      "bpf_map_create()", "error:%s\n", strerror(errno));
96
97	value_size = is_pcpu ? sizeof(value) : sizeof(int);
98	keys = malloc(max_entries * sizeof(int));
99	if (is_pcpu)
100		values = pcpu_values;
101	else
102		values = malloc(max_entries * sizeof(int));
103	visited = malloc(max_entries * sizeof(int));
104	CHECK(!keys || !values || !visited, "malloc()",
105	      "error:%s\n", strerror(errno));
106
107	/* test 1: lookup/delete an empty hash table, -ENOENT */
108	count = max_entries;
109	err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys,
110					      values, &count, &opts);
111	CHECK((err && errno != ENOENT), "empty map",
112	      "error: %s\n", strerror(errno));
113
114	/* populate elements to the map */
115	map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
116
117	/* test 2: lookup/delete with count = 0, success */
118	count = 0;
119	err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys,
120					      values, &count, &opts);
121	CHECK(err, "count = 0", "error: %s\n", strerror(errno));
122
123	/* test 3: lookup/delete with count = max_entries, success */
124	memset(keys, 0, max_entries * sizeof(*keys));
125	memset(values, 0, max_entries * value_size);
126	count = max_entries;
127	err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys,
128					      values, &count, &opts);
129	CHECK((err && errno != ENOENT), "count = max_entries",
130	       "error: %s\n", strerror(errno));
131	CHECK(count != max_entries, "count = max_entries",
132	      "count = %u, max_entries = %u\n", count, max_entries);
133	map_batch_verify(visited, max_entries, keys, values, is_pcpu);
134
135	/* bpf_map_get_next_key() should return -ENOENT for an empty map. */
136	err = bpf_map_get_next_key(map_fd, NULL, &key);
137	CHECK(!err, "bpf_map_get_next_key()", "error: %s\n", strerror(errno));
138
139	/* test 4: lookup/delete in a loop with various steps. */
140	total_success = 0;
141	for (step = 1; step < max_entries; step++) {
142		map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
143		memset(keys, 0, max_entries * sizeof(*keys));
144		memset(values, 0, max_entries * value_size);
145		total = 0;
146		/* iteratively lookup/delete elements with 'step'
147		 * elements each
148		 */
149		count = step;
150		nospace_err = false;
151		while (true) {
152			err = bpf_map_lookup_batch(map_fd,
153						   total ? &batch : NULL,
154						   &batch, keys + total,
155						   values +
156						   total * value_size,
157						   &count, &opts);
158			/* It is possible that we are failing due to buffer size
159			 * not big enough. In such cases, let us just exit and
160			 * go with large steps. Not that a buffer size with
161			 * max_entries should always work.
162			 */
163			if (err && errno == ENOSPC) {
164				nospace_err = true;
165				break;
166			}
167
168			CHECK((err && errno != ENOENT), "lookup with steps",
169			      "error: %s\n", strerror(errno));
170
171			total += count;
172			if (err)
173				break;
174
175		}
176		if (nospace_err == true)
177			continue;
178
179		CHECK(total != max_entries, "lookup with steps",
180		      "total = %u, max_entries = %u\n", total, max_entries);
181		map_batch_verify(visited, max_entries, keys, values, is_pcpu);
182
183		total = 0;
184		count = step;
185		while (total < max_entries) {
186			if (max_entries - total < step)
187				count = max_entries - total;
188			err = bpf_map_delete_batch(map_fd,
189						   keys + total,
190						   &count, &opts);
191			CHECK((err && errno != ENOENT), "delete batch",
192			      "error: %s\n", strerror(errno));
193			total += count;
194			if (err)
195				break;
196		}
197		CHECK(total != max_entries, "delete with steps",
198		      "total = %u, max_entries = %u\n", total, max_entries);
199
200		/* check map is empty, errono == ENOENT */
201		err = bpf_map_get_next_key(map_fd, NULL, &key);
202		CHECK(!err || errno != ENOENT, "bpf_map_get_next_key()",
203		      "error: %s\n", strerror(errno));
204
205		/* iteratively lookup/delete elements with 'step'
206		 * elements each
207		 */
208		map_batch_update(map_fd, max_entries, keys, values, is_pcpu);
209		memset(keys, 0, max_entries * sizeof(*keys));
210		memset(values, 0, max_entries * value_size);
211		total = 0;
212		count = step;
213		nospace_err = false;
214		while (true) {
215			err = bpf_map_lookup_and_delete_batch(map_fd,
216							total ? &batch : NULL,
217							&batch, keys + total,
218							values +
219							total * value_size,
220							&count, &opts);
221			/* It is possible that we are failing due to buffer size
222			 * not big enough. In such cases, let us just exit and
223			 * go with large steps. Not that a buffer size with
224			 * max_entries should always work.
225			 */
226			if (err && errno == ENOSPC) {
227				nospace_err = true;
228				break;
229			}
230
231			CHECK((err && errno != ENOENT), "lookup with steps",
232			      "error: %s\n", strerror(errno));
233
234			total += count;
235			if (err)
236				break;
237		}
238
239		if (nospace_err == true)
240			continue;
241
242		CHECK(total != max_entries, "lookup/delete with steps",
243		      "total = %u, max_entries = %u\n", total, max_entries);
244
245		map_batch_verify(visited, max_entries, keys, values, is_pcpu);
246		err = bpf_map_get_next_key(map_fd, NULL, &key);
247		CHECK(!err, "bpf_map_get_next_key()", "error: %s\n",
248		      strerror(errno));
249
250		total_success++;
251	}
252
253	CHECK(total_success == 0, "check total_success",
254	      "unexpected failure\n");
255	free(keys);
256	free(visited);
257	if (!is_pcpu)
258		free(values);
259	close(map_fd);
260}
261
262void htab_map_batch_ops(void)
263{
264	__test_map_lookup_and_delete_batch(false);
265	printf("test_%s:PASS\n", __func__);
266}
267
268void htab_percpu_map_batch_ops(void)
269{
270	__test_map_lookup_and_delete_batch(true);
271	printf("test_%s:PASS\n", __func__);
272}
273
274void test_htab_map_batch_ops(void)
275{
276	htab_map_batch_ops();
277	htab_percpu_map_batch_ops();
278}
279