xref: /linux/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c (revision 49bda4826843be0ef97a162009a29ea3a63f3935)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 /* kselftest for allocinfo ioctl
4  * allocinfo ioctl retrieves allocinfo data through ioctl
5  * Copyright (C) 2026 Google, Inc.
6  */
7 
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stdbool.h>
14 #include <unistd.h>
15 #include <sys/ioctl.h>
16 #include <linux/types.h>
17 #include <linux/alloc_tag.h>
18 #include "../kselftest.h"
19 
20 #define MAX_LINE_LEN		512
21 #define ALLOCINFO_PROC		"/proc/allocinfo"
22 
23 enum ioctl_ret {
24 	IOCTL_SUCCESS = 0,
25 	IOCTL_FAILURE = 1,
26 	IOCTL_INVALID_DATA = 2,
27 };
28 
29 #define VEC_MAX_ENTRIES 32
30 
31 struct allocinfo_tag_data_vec {
32 	struct allocinfo_tag_data tag[VEC_MAX_ENTRIES];
33 	__u64 count;
34 };
35 
__allocinfo_get_content_id(int dev_fd,struct allocinfo_content_id * params)36 static inline int __allocinfo_get_content_id(int dev_fd, struct allocinfo_content_id *params)
37 {
38 	return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params);
39 }
40 
__allocinfo_get_at(int dev_fd,struct allocinfo_get_at * params)41 static inline int __allocinfo_get_at(int dev_fd, struct allocinfo_get_at *params)
42 {
43 	return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params);
44 }
45 
__allocinfo_get_next(int dev_fd,struct allocinfo_tag_data * params)46 static inline int __allocinfo_get_next(int dev_fd, struct allocinfo_tag_data *params)
47 {
48 	return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params);
49 }
50 
match_entry(const struct allocinfo_tag_data * procfs_entry,const struct allocinfo_tag_data * tag_data,bool match_bytes,bool match_calls,bool match_lineno,bool match_function,bool match_filename)51 static bool match_entry(const struct allocinfo_tag_data *procfs_entry,
52 			const struct allocinfo_tag_data *tag_data,
53 			bool match_bytes, bool match_calls, bool match_lineno,
54 			bool match_function, bool match_filename)
55 {
56 	if (match_bytes && tag_data->counter.bytes != procfs_entry->counter.bytes) {
57 		ksft_print_msg("size retrieved through ioctl does not match procfs\n");
58 		return false;
59 	}
60 
61 	if (match_calls && tag_data->counter.calls != procfs_entry->counter.calls) {
62 		ksft_print_msg("call count retrieved through ioctl does not match procfs\n");
63 		return false;
64 	}
65 
66 	if (match_lineno && tag_data->tag.lineno != procfs_entry->tag.lineno) {
67 		ksft_print_msg("lineno retrieved through ioctl does not match procfs\n");
68 		return false;
69 	}
70 
71 	if (match_function &&
72 	    strncmp(tag_data->tag.function, procfs_entry->tag.function, ALLOCINFO_STR_SIZE)) {
73 		ksft_print_msg("function retrieved through ioctl does not match procfs\n");
74 		return false;
75 	}
76 
77 	if (match_filename &&
78 	    strncmp(tag_data->tag.filename, procfs_entry->tag.filename, ALLOCINFO_STR_SIZE)) {
79 		ksft_print_msg("filename retrieved through ioctl does not match procfs\n");
80 		return false;
81 	}
82 	return true;
83 }
84 
match_entries(const struct allocinfo_tag_data_vec * procfs_entries,const struct allocinfo_tag_data_vec * tags,bool match_bytes,bool match_calls,bool match_lineno,bool match_function,bool match_filename)85 static bool match_entries(const struct allocinfo_tag_data_vec *procfs_entries,
86 			  const struct allocinfo_tag_data_vec *tags,
87 			  bool match_bytes, bool match_calls, bool match_lineno,
88 			  bool match_function, bool match_filename)
89 {
90 	__u64 i;
91 
92 	if (procfs_entries->count != tags->count) {
93 		ksft_print_msg("Entry count mismatch. ioctl entries: %llu, proc entries: %llu\n",
94 			       tags->count, procfs_entries->count);
95 		return false;
96 	}
97 	for (i = 0; i < procfs_entries->count; i++) {
98 		if (!match_entry(&procfs_entries->tag[i], &tags->tag[i],
99 				 match_bytes, match_calls, match_lineno,
100 				 match_function, match_filename)) {
101 			ksft_print_msg("%lluth entry does not match.\n", i);
102 			return false;
103 		}
104 	}
105 	return true;
106 }
107 
allocinfo_str(const char * str)108 static const char *allocinfo_str(const char *str)
109 {
110 	size_t len = strlen(str);
111 
112 	if (len >= ALLOCINFO_STR_SIZE)
113 		str += (len - ALLOCINFO_STR_SIZE) + 1;
114 	return str;
115 }
116 
allocinfo_copy_str(char * dest,const char * src)117 static void allocinfo_copy_str(char *dest, const char *src)
118 {
119 	strncpy(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE - 1);
120 	dest[ALLOCINFO_STR_SIZE - 1] = '\0';
121 }
122 
get_filtered_procfs_entries(struct allocinfo_tag_data_vec * procfs_entries,const struct allocinfo_filter * filter)123 static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec *procfs_entries,
124 				       const struct allocinfo_filter *filter)
125 {
126 	FILE *fp = fopen(ALLOCINFO_PROC, "r");
127 	char line[MAX_LINE_LEN];
128 	int matches;
129 	struct allocinfo_tag_data procfs_entry;
130 
131 	if (!fp) {
132 		ksft_print_msg("Failed to open " ALLOCINFO_PROC " for reading\n");
133 		return 1;
134 	}
135 	memset(procfs_entries, 0, sizeof(*procfs_entries));
136 	while (fgets(line, sizeof(line), fp) && procfs_entries->count < VEC_MAX_ENTRIES) {
137 		char filename[MAX_LINE_LEN];
138 		char function[MAX_LINE_LEN];
139 
140 		memset(&procfs_entry, 0, sizeof(procfs_entry));
141 		matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s",
142 				 &procfs_entry.counter.bytes,
143 				 &procfs_entry.counter.calls,
144 				 filename,
145 				 &procfs_entry.tag.lineno,
146 				 function);
147 
148 		if (matches != 5)
149 			continue;
150 
151 		allocinfo_copy_str(procfs_entry.tag.filename, filename);
152 		allocinfo_copy_str(procfs_entry.tag.function, function);
153 
154 		if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) {
155 			if (strncmp(procfs_entry.tag.filename,
156 				    filter->fields.filename, ALLOCINFO_STR_SIZE))
157 				continue;
158 		}
159 		if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) {
160 			if (strncmp(procfs_entry.tag.function,
161 				    filter->fields.function, ALLOCINFO_STR_SIZE))
162 				continue;
163 		}
164 		if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) {
165 			if (procfs_entry.tag.lineno != filter->fields.lineno)
166 				continue;
167 		}
168 		if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) {
169 			if (procfs_entry.counter.bytes < filter->min_size)
170 				continue;
171 		}
172 		if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) {
173 			if (procfs_entry.counter.bytes > filter->max_size)
174 				continue;
175 		}
176 
177 		memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry,
178 		       sizeof(procfs_entry));
179 	}
180 	fclose(fp);
181 	return 0;
182 }
183 
get_filtered_ioctl_entries(struct allocinfo_tag_data_vec * tags,const struct allocinfo_filter * filter,__u64 start_pos)184 static enum ioctl_ret get_filtered_ioctl_entries(struct allocinfo_tag_data_vec *tags,
185 						 const struct allocinfo_filter *filter,
186 						 __u64 start_pos)
187 {
188 	int fd = open(ALLOCINFO_PROC, O_RDONLY);
189 
190 	if (fd < 0) {
191 		ksft_print_msg("Failed to open " ALLOCINFO_PROC " for IOCTL\n");
192 		return IOCTL_FAILURE;
193 	}
194 
195 	struct allocinfo_content_id start_cont_id, end_cont_id;
196 	struct allocinfo_get_at get_at_params;
197 	const int max_retries = 10;
198 	int retry_count = 0;
199 	int status;
200 
201 	/*
202 	 * __allocinfo_get_content_id may return different values if a kernel module was loaded
203 	 * between the two calls. If that happens, the data gathered cannot be considered consistent
204 	 * and hence needs to be fetched again to avoid flakiness.
205 	 */
206 	do {
207 		if (__allocinfo_get_content_id(fd, &start_cont_id)) {
208 			ksft_print_msg("allocinfo_get_content_id failed\n");
209 			status = IOCTL_FAILURE;
210 			break;
211 		}
212 
213 		memset(tags, 0, sizeof(*tags));
214 		memset(&get_at_params, 0, sizeof(get_at_params));
215 		memcpy(&get_at_params.filter, filter, sizeof(*filter));
216 		get_at_params.pos = start_pos;
217 		if (__allocinfo_get_at(fd, &get_at_params)) {
218 			ksft_print_msg("allocinfo_get_at failed\n");
219 			status = IOCTL_FAILURE;
220 			break;
221 		}
222 		memcpy(&tags->tag[tags->count++], &get_at_params.data, sizeof(get_at_params.data));
223 
224 		while (tags->count < VEC_MAX_ENTRIES &&
225 		       __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
226 			tags->count++;
227 
228 		if (__allocinfo_get_content_id(fd, &end_cont_id)) {
229 			ksft_print_msg("allocinfo_get_content_id failed\n");
230 			status = IOCTL_FAILURE;
231 			break;
232 		}
233 
234 		if (start_cont_id.id == end_cont_id.id) {
235 			status = IOCTL_SUCCESS;
236 		} else {
237 			ksft_print_msg("allocinfo_get_content_id mismatch, retrying...\n");
238 			status = IOCTL_INVALID_DATA;
239 		}
240 	} while (status == IOCTL_INVALID_DATA && retry_count++ < max_retries);
241 
242 	close(fd);
243 	return status;
244 }
245 
run_filter_test(const struct allocinfo_filter * filter)246 static int run_filter_test(const struct allocinfo_filter *filter)
247 {
248 	struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
249 	struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
250 	int ioctl_status;
251 	int ret = KSFT_PASS;
252 
253 	if (!tags || !procfs_entries) {
254 		ksft_print_msg("Memory allocation failed.\n");
255 		ret = KSFT_FAIL;
256 		goto exit;
257 	}
258 
259 	if (get_filtered_procfs_entries(procfs_entries, filter)) {
260 		ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
261 		ret = KSFT_SKIP;
262 		goto exit;
263 	}
264 
265 	if (procfs_entries->count == 0) {
266 		ksft_print_msg("No entries found in " ALLOCINFO_PROC ", skipping test\n");
267 		ret = KSFT_SKIP;
268 		goto exit;
269 	}
270 
271 	ioctl_status = get_filtered_ioctl_entries(tags, filter, 0);
272 	if (ioctl_status == IOCTL_INVALID_DATA) {
273 		ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
274 		ret = KSFT_SKIP;
275 		goto exit;
276 	}
277 	if (ioctl_status == IOCTL_FAILURE) {
278 		ksft_print_msg("Error retrieving IOCTL entries.\n");
279 		ret = KSFT_FAIL;
280 		goto exit;
281 	}
282 
283 	if (!match_entries(procfs_entries, tags, false, false, true, true, true))
284 		ret = KSFT_FAIL;
285 
286 exit:
287 	free(tags);
288 	free(procfs_entries);
289 	return ret;
290 }
291 
test_filename_filter(void)292 static int test_filename_filter(void)
293 {
294 	struct allocinfo_filter filter;
295 	const char *target_filename = "mm/memory.c";
296 
297 	memset(&filter, 0, sizeof(filter));
298 	filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME;
299 	strncpy(filter.fields.filename, target_filename, ALLOCINFO_STR_SIZE);
300 
301 	return run_filter_test(&filter);
302 }
303 
test_function_filter(void)304 static int test_function_filter(void)
305 {
306 	struct allocinfo_filter filter;
307 	const char *target_function = "dup_mm";
308 
309 	memset(&filter, 0, sizeof(filter));
310 	filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
311 	strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
312 
313 	return run_filter_test(&filter);
314 }
315 
test_size_filter(void)316 static int test_size_filter(void)
317 {
318 	int fd;
319 	struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
320 	struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
321 	struct allocinfo_filter filter;
322 	int ret = KSFT_PASS;
323 	__u64 target_size, i, pos;
324 	struct allocinfo_tag_data *found_tag = NULL;
325 	const char *target_function = "do_init_module";
326 	struct allocinfo_content_id start_cont_id, end_cont_id;
327 	int retry = 0;
328 	const int max_retries = 10;
329 
330 	if (!tags || !procfs_entries) {
331 		ksft_print_msg("Memory allocation failed.\n");
332 		ret = KSFT_FAIL;
333 		goto freemem;
334 	}
335 
336 	fd = open(ALLOCINFO_PROC, O_RDONLY);
337 	if (fd < 0) {
338 		ksft_print_msg("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
339 		ret = KSFT_SKIP;
340 		goto freemem;
341 	}
342 
343 	do {
344 		found_tag = NULL;
345 		pos = 0;
346 
347 		if (__allocinfo_get_content_id(fd, &start_cont_id)) {
348 			ksft_print_msg("allocinfo_get_content_id failed\n");
349 			ret = KSFT_FAIL;
350 			goto exit;
351 		}
352 
353 		memset(&filter, 0, sizeof(filter));
354 		filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
355 		strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
356 
357 		if (get_filtered_procfs_entries(procfs_entries, &filter)) {
358 			ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
359 			ret = KSFT_SKIP;
360 			goto exit;
361 		}
362 
363 		if (procfs_entries->count == 0) {
364 			ksft_print_msg("Function %s not found in procfs\n", target_function);
365 			ret = KSFT_SKIP;
366 			goto exit;
367 		}
368 
369 		target_size = procfs_entries->tag[0].counter.bytes;
370 
371 		memset(&filter, 0, sizeof(filter));
372 		filter.mask |= ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE;
373 		filter.min_size = target_size;
374 		filter.max_size = target_size;
375 
376 		while (1) {
377 			struct allocinfo_get_at get_at_params;
378 
379 			memset(&get_at_params, 0, sizeof(get_at_params));
380 			memcpy(&get_at_params.filter, &filter, sizeof(filter));
381 			get_at_params.pos = pos;
382 
383 			if (__allocinfo_get_at(fd, &get_at_params))
384 				break;
385 
386 			tags->count = 0;
387 			memcpy(&tags->tag[tags->count++], &get_at_params.data,
388 			       sizeof(get_at_params.data));
389 
390 			while (tags->count < VEC_MAX_ENTRIES &&
391 			       __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
392 				tags->count++;
393 
394 			for (i = 0; i < tags->count; i++) {
395 				if (strcmp(tags->tag[i].tag.function, target_function) == 0) {
396 					found_tag = &tags->tag[i];
397 					break;
398 				}
399 			}
400 
401 			if (found_tag || tags->count < VEC_MAX_ENTRIES)
402 				break;
403 
404 			pos += tags->count;
405 		}
406 
407 		if (__allocinfo_get_content_id(fd, &end_cont_id)) {
408 			ksft_print_msg("allocinfo_get_content_id failed\n");
409 			ret = KSFT_FAIL;
410 			goto exit;
411 		}
412 
413 		if (start_cont_id.id == end_cont_id.id)
414 			break;
415 
416 		ksft_print_msg("Module load detected during size verification, retrying...\n");
417 	} while (retry++ < max_retries);
418 
419 	if (start_cont_id.id == end_cont_id.id && !found_tag) {
420 		ksft_print_msg("Entry with function %s not found in IOCTL results\n",
421 			       target_function);
422 		ret = KSFT_FAIL;
423 	} else if (start_cont_id.id != end_cont_id.id) {
424 		ksft_print_msg("Failed to match content_ids for procfs and IOCTL, skipping...\n");
425 		ret = KSFT_SKIP;
426 	} else if (found_tag && found_tag->counter.bytes != target_size) {
427 		ksft_print_msg("IOCTL entry size %llu does not match target size %llu\n",
428 			       found_tag->counter.bytes, target_size);
429 		ret = KSFT_FAIL;
430 	}
431 
432 exit:
433 	close(fd);
434 freemem:
435 	free(tags);
436 	free(procfs_entries);
437 	return ret;
438 }
439 
test_lineno_filter(void)440 static int test_lineno_filter(void)
441 {
442 	struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
443 	struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
444 	struct allocinfo_filter filter;
445 	enum ioctl_ret ioctl_status;
446 	int ret = KSFT_PASS;
447 	__u64 target_lineno, i;
448 	struct allocinfo_tag_data *target_tag;
449 	bool found = false;
450 
451 	if (!tags || !procfs_entries) {
452 		ksft_print_msg("Memory allocation failed.\n");
453 		ret = KSFT_FAIL;
454 		goto exit;
455 	}
456 
457 	memset(&filter, 0, sizeof(filter));
458 
459 	if (get_filtered_procfs_entries(procfs_entries, &filter)) {
460 		ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
461 		ret = KSFT_SKIP;
462 		goto exit;
463 	}
464 	if (procfs_entries->count == 0) {
465 		ksft_print_msg("Could not retrieve procfs entries\n");
466 		ret = KSFT_SKIP;
467 		goto exit;
468 	}
469 	/*
470 	 * We depend on the procfs results to determine the line number for the filter before
471 	 * making the ioctl query. Hence, we cannot reuse run_filter_test here.
472 	 */
473 	target_tag = &procfs_entries->tag[0];
474 	target_lineno = target_tag->tag.lineno;
475 
476 	filter.mask |= ALLOCINFO_FILTER_MASK_LINENO;
477 	filter.fields.lineno = target_lineno;
478 
479 	ioctl_status = get_filtered_ioctl_entries(tags, &filter, 0);
480 	if (ioctl_status == IOCTL_INVALID_DATA) {
481 		ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
482 		ret = KSFT_SKIP;
483 		goto exit;
484 	}
485 	if (ioctl_status == IOCTL_FAILURE) {
486 		ksft_print_msg("Error retrieving IOCTL entries.\n");
487 		ret = KSFT_FAIL;
488 		goto exit;
489 	}
490 
491 	for (i = 0; i < tags->count; i++) {
492 		if (tags->tag[i].tag.lineno != target_lineno) {
493 			ksft_print_msg("IOCTL entry %llu has incorrect lineno %llu.\n",
494 				       i, tags->tag[i].tag.lineno);
495 			ret = KSFT_FAIL;
496 			goto exit;
497 		}
498 
499 		if (strncmp(tags->tag[i].tag.function, target_tag->tag.function,
500 			    ALLOCINFO_STR_SIZE) == 0 &&
501 		    strncmp(tags->tag[i].tag.filename, target_tag->tag.filename,
502 			    ALLOCINFO_STR_SIZE) == 0)
503 			found = true;
504 	}
505 
506 	if (!found) {
507 		ksft_print_msg("Original procfs entry not found in IOCTL lineno filter results.\n");
508 		ret = KSFT_FAIL;
509 	}
510 
511 exit:
512 	free(tags);
513 	free(procfs_entries);
514 	return ret;
515 }
516 
main(int argc,char * argv[])517 int main(int argc, char *argv[])
518 {
519 	int ret;
520 
521 	ksft_set_plan(4);
522 
523 	ret = test_filename_filter();
524 	if (ret == KSFT_SKIP)
525 		ksft_test_result_skip("Skipping test_filename_filter\n");
526 	else
527 		ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n");
528 
529 	ret = test_function_filter();
530 	if (ret == KSFT_SKIP)
531 		ksft_test_result_skip("Skipping test_function_filter\n");
532 	else
533 		ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
534 
535 	ret = test_size_filter();
536 	if (ret == KSFT_SKIP)
537 		ksft_test_result_skip("Skipping test_size_filter\n");
538 	else
539 		ksft_test_result(ret == KSFT_PASS, "test_size_filter\n");
540 
541 	ret = test_lineno_filter();
542 	if (ret == KSFT_SKIP)
543 		ksft_test_result_skip("Skipping test_lineno_filter\n");
544 	else
545 		ksft_test_result(ret == KSFT_PASS, "test_lineno_filter\n");
546 
547 	ksft_finished();
548 }
549