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 
14 static 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 
41 static 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 
77 void __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 
262 void htab_map_batch_ops(void)
263 {
264 	__test_map_lookup_and_delete_batch(false);
265 	printf("test_%s:PASS\n", __func__);
266 }
267 
268 void htab_percpu_map_batch_ops(void)
269 {
270 	__test_map_lookup_and_delete_batch(true);
271 	printf("test_%s:PASS\n", __func__);
272 }
273 
274 void test_htab_map_batch_ops(void)
275 {
276 	htab_map_batch_ops();
277 	htab_percpu_map_batch_ops();
278 }
279