[PATCH v5,resend i-g-t 1/6] lib/igt_dir: Directory processing and flexible file handling

Kamil Konieczny kamil.konieczny at linux.intel.com
Thu Jul 10 10:52:06 UTC 2025


Hi Peter,
On 2025-07-08 at 16:40:33 +0200, Peter Senna Tschudin wrote:
> Introduces new utilities to facilitate reading and processing files
> within a directory, giving test writers greater control over file
> selection and processing. For example, to read and discard all files

Please move these examples to library comment in igt_dir.c
imho it is better to have this documentation there. You could
look into igt_core.c for inspiration how could this be documented.

> from debugfs:
> 
Add a note about first fixture:

First fixture:

igt_fixture {
>   fd = drm_open_driver_master(DRIVER_ANY);
>   debugfs = igt_debugfs_dir(fd);
}


> 
>   igt_dir = igt_dir_create(debugfs);
>   igt_dir_scan_dirfd(igt_dir, -1); // -1 means unlimited scan depth
>   igt_dir_process_files(igt_dir, NULL, NULL);

Add also:

Last fixture:

igt_fixture {
    igt_dir_destroy(igt_dir);
    close(debugfs);
    drm_close_driver(fd);
}

> 
> The igt_dir_scan_dirfd() function builds a linked list of files (using
> igt_list), making it easy to add or remove specific files before
> processing. If you only want to process a predetermined set of files,
> you can skip the scan step and add the files directly to the list.
> 
> The last two parameters of igt_dir_process_files() specify a callback
> function and user data. If the callback is NULL, a default "read and
> discard" function is used.
> 
> Alternatively a "_simple" interface is also available. This function
> encapsulate the calls to igt_dir_create(), igt_dir_scan_dirfd(),
> igt_dir_process_files(), and igt_dir_destroy(). For using the "_simple"
> interface:
> 
>   fd = drm_open_driver_master(DRIVER_ANY);
>   debugfs = igt_debugfs_dir(fd);
>   igt_dir_process_files_simple(debugfs);

Above is also better placed as a comment in lib itself.

> 
> Cc: lucas.demarchi at intel.com
> Cc: rodrigo.vivi at intel.com
> Cc: kamil.konieczny at linux.intel.com
> Cc: katarzyna.piecielska at intel.com
> Cc: zbigniew.kempczynski at intel.com
> Cc: michal.wajdeczko at intel.com
> Cc: karthik.b.s at intel.com
> Reviewed-by: Jan Sokolowski <jan.sokolowski at intel.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna at linux.intel.com>
> ---
> v5:
>  - Added igt_dir_process_files_simple()
> v4:
>  - unchanged from v3
> v3:
>  - unchanged from v2
> v2:
>  - changed style of comparison to NULL
> 
>  lib/igt_dir.c   | 287 ++++++++++++++++++++++++++++++++++++++++++++++++
>  lib/igt_dir.h   |  62 +++++++++++
>  lib/meson.build |   1 +
>  3 files changed, 350 insertions(+)
>  create mode 100644 lib/igt_dir.c
>  create mode 100644 lib/igt_dir.h
> 
> diff --git a/lib/igt_dir.c b/lib/igt_dir.c
> new file mode 100644
> index 000000000..bbb984ab4
> --- /dev/null
> +++ b/lib/igt_dir.c
> @@ -0,0 +1,287 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright © 2025 Intel Corporation
> + */
> +
> +#include <dirent.h>
> +#include <fcntl.h>
> +
> +#include "igt.h"
> +#include "lib/igt_dir.h"

Both headers are in igt lib dir, so:

#include "igt_dir.h"

> +
> +/**
> + * igt_dir_get_fd_path: Get the path of a file descriptor
> + * @fd: file descriptor to get the path for
> + * @path: buffer to store the path
> + * @path_len: length of the buffer
> + *
> + * Returns: 0 on success, a negative error code on failure
> + */
> +int igt_dir_get_fd_path(int fd, char *path, size_t path_len)
> +{
> +	ssize_t len;
> +	char proc_path[64];
> +
> +	snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd);
> +	len = readlink(proc_path, path, path_len - 1);
> +	if (len == -1)
> +		return -1;
> +
> +	path[path_len] = '\0';

Add newline.

> +	return 0;
> +}
> +
> +/**
> + * igt_dir_callback_read_discard: Default callback function for reading and
> + *				  discarding file contents
> + * @filename: Path to the file
> + * @callback_data: Optional pointer to user-defined data passed to the callback
> + *
> + * Returns: 0 on success, a negative error code on failure
> + */
> +int igt_dir_callback_read_discard(const char *filename,
> +				  void *callback_data)
> +{
> +	int fd;
> +	char buf[4096];
> +	ssize_t bytes_read;
> +
> +	fd = open(filename, O_RDONLY);
> +	if (fd < 0) {
> +		igt_debug("Failed to open file %s\n", filename);
> +		return -1;
> +	}

Add newline.

> +	bytes_read = read(fd, buf, sizeof(buf) - 1);
> +	if (bytes_read < 0) {
> +		igt_debug("Failed to read file %s\n", filename);
> +		close(fd);
> +		return -1;
> +	}

Add newline.

> +	buf[bytes_read] = '\0';
> +	igt_debug("Read %zd bytes from file %s: %s\n", bytes_read,
> +		  filename, buf);
> +	close(fd);

Add newline.

> +	return 0;
> +}
> +
> +/**
> + * igt_dir_create: Create a new igt_dir_t struct
> + * @dirfd: file descriptor of the root directory
> + *
> + * Returns: Pointer to the new igt_dir_t struct, or NULL on failure
> + */
> +igt_dir_t *igt_dir_create(int dirfd)
> +{
> +	igt_dir_t *config;
> +	size_t path_len = 512;
> +	char path[path_len];
> +
> +	config = malloc(sizeof(igt_dir_t));
> +	if (!config)
> +		return NULL;
> +
> +	config->dirfd = dirfd;
> +
> +	igt_dir_get_fd_path(dirfd, path, path_len);
> +	igt_require(path[0] != '\0');
> +
> +	config->root_path = malloc(path_len);
> +	if (!config->root_path) {
> +		free(config);
> +		return NULL;
> +	}
> +
> +	strncpy(config->root_path, path, path_len);
> +
> +	IGT_INIT_LIST_HEAD(&config->file_list_head);
> +
> +	config->callback = NULL;
> +
> +	return config;
> +}
> +
> +static int _igt_dir_scan_dirfd(igt_dir_t *config, int scan_maxdepth,
> +			       int depth, const char *current_path)
> +{
> +	struct dirent *entry;
> +	igt_dir_file_list_t *file_list_entry;
> +	DIR *dirp;
> +	int dirfd;
> +	int ret = 0;
> +
> +	if (depth > scan_maxdepth && scan_maxdepth != -1)
> +		return 0;

Add igt_debug about reaching max scandepth.

> +
> +	if (!current_path) {
> +		igt_debug("Invalid current path\n");
> +		return -1;
> +	}
> +
> +	dirfd = open(current_path, O_RDONLY | O_DIRECTORY);
> +	if (dirfd < 0) {
> +		igt_debug("Failed to open directory %s\n", current_path);
> +		return -1;
> +	}
> +
> +	dirp = fdopendir(dirfd);
> +	if (!dirp) {
> +		igt_debug("Failed to fdopendir %s\n", current_path);
> +		close(dirfd);
> +		return -1;
> +	}
> +
> +	while ((entry = readdir(dirp))) {
> +		char entry_path[PATH_MAX];
> +
> +		if (strcmp(entry->d_name, ".") == 0 ||
> +		    strcmp(entry->d_name, "..") == 0)
> +			continue;
> +
> +		snprintf(entry_path, sizeof(entry_path),
> +			 "%s/%s", current_path, entry->d_name);
> +
> +		if (entry->d_type == DT_DIR) {
> +			ret = _igt_dir_scan_dirfd(config, scan_maxdepth,
> +						  depth + 1, entry_path);
> +			if (ret)
> +				break;
> +		} else {
> +			/* Compute path relative to the scan root */
> +			const char *relative_path = entry_path +
> +						    strlen(config->root_path);
> +			if (*relative_path == '/')
> +				relative_path++; /* skip leading slash */
> +
> +			file_list_entry = malloc(sizeof(igt_dir_file_list_t));
> +			if (!file_list_entry) {
> +				igt_debug("Failed to allocate memory for file list entry\n");
> +				continue;
> +			}
> +			file_list_entry->relative_path = strdup(relative_path);
> +			file_list_entry->match = true;
> +			igt_list_add(&file_list_entry->link,
> +				     &config->file_list_head);
> +		}
> +	}
> +
> +	closedir(dirp);
> +	close(dirfd);

Add newline.

> +	return ret;
> +}
> +
> +/**
> + * igt_dir_scan_dirfd: Perform a directory scan based on config.
> + * @config: Pointer to the igt_dir struct
> + * @scan_maxdepth: Maximum depth to scan the directory. -1 means no limit
> + *
> + * Returns: 0 on success, a negative error code on failure
> + */
> +int igt_dir_scan_dirfd(igt_dir_t *config, int scan_maxdepth)
> +{
> +	igt_require(config);
> +	igt_require(config->root_path);
> +	igt_require(config->dirfd >= 0);
> +	igt_require(scan_maxdepth >= -1);
> +	igt_require(scan_maxdepth != 0);
> +
> +	/* If the linked list is not empty, clean it first */
> +	if (!igt_list_empty(&config->file_list_head)) {
> +		igt_dir_file_list_t *file_list_entry, *tmp;
> +
> +		igt_list_for_each_entry_safe(file_list_entry, tmp,
> +					     &config->file_list_head, link) {
> +			free(file_list_entry->relative_path);
> +			free(file_list_entry);
> +		}
> +	}
> +
> +	return _igt_dir_scan_dirfd(config, scan_maxdepth, 0, config->root_path);
> +}
> +
> +/**
> + * igt_dir_process_files: Process files in the directory
> + * @config: Pointer to the igt_dir struct
> + * @callback: Callback function to process each file
> + * @callback_data: Optional pointer to user-defined data passed to the callback
> + *
> + * Returns: 0 on success, a negative error code on failure
> + */
> +int igt_dir_process_files(igt_dir_t *config,
> +			 igt_dir_file_callback callback,

Align.

> +			 void *callback_data)

Same.

> +{
> +	igt_dir_file_list_t *file_list_entry;
> +	int ret = 0;
> +
> +	igt_require(config);
> +	igt_require(config->root_path);
> +	igt_require(config->dirfd >= 0);
> +
> +	if (!callback)
> +		callback = igt_dir_callback_read_discard;
> +
> +	igt_list_for_each_entry(file_list_entry, &config->file_list_head, link) {
> +		/* Only if match is true */
> +		if (file_list_entry->match) {
> +			char full_path[PATH_MAX];
> +
> +			snprintf(full_path, sizeof(full_path),
> +				 "%s/%s", config->root_path,
> +				 file_list_entry->relative_path);
> +			ret = callback(full_path, callback_data);
> +			if (ret)
> +				break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * igt_dir_destroy: Destroy the igt_dir struct
> + * @config: Pointer to the igt_dir struct
> + *
> + * Returns: 0 on success, a negative error code on failure
> + */
> +void igt_dir_destroy(igt_dir_t *config)
> +{
> +	igt_dir_file_list_t *file_list_entry, *tmp;
> +
> +	igt_require(config);
> +
> +	igt_list_for_each_entry_safe(file_list_entry, tmp,
> +				 &config->file_list_head, link) {

Align.

> +		free(file_list_entry->relative_path);
> +		free(file_list_entry);
> +	}
> +
> +	free(config->root_path);
> +	free(config);
> +}
> +
> +/**
> + * igt_dir_process_files_simple: Process files in the directory using the
> + *				 default callback to read and discard file
> + *				 contents.
> + *
> + * @dirfd: file descriptor of the root directory
> + *
> + * Returns: 0 on success, a negative error code on failure
> + */
> +int igt_dir_process_files_simple(int dirfd)
> +{
> +	igt_dir_t *config;
> +	int ret;
> +
> +	config = igt_dir_create(dirfd);
> +	if (!config)
> +		return -1;
> +
> +	igt_dir_scan_dirfd(config, -1);
> +
> +	/* Use the default callback to read and discard file contents */
> +	ret = igt_dir_process_files(config, NULL, NULL);
> +
> +	igt_dir_destroy(config);

Add newline.

> +	return ret;
> +}
> diff --git a/lib/igt_dir.h b/lib/igt_dir.h
> new file mode 100644
> index 000000000..5e36d8af8
> --- /dev/null
> +++ b/lib/igt_dir.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: MIT
> + * Copyright © 2025 Intel Corporation
> + */
> +
> +#ifndef IGT_DIR_H
> +#define IGT_DIR_H
> +
> +#include "igt_list.h"
> +
> +/**
> + * Callback function type for processing files
> + * The callback is blocking, meaning traversal waits for it to return
> + * before proceeding to the next file
> + * @filename: Path to the file
> + * @callback_data: Optional pointer to user-defined data passed to the callback
> + *
> + * Returns:
> + * 0 on success, a negative error code on failure.
> + */
> +typedef int (*igt_dir_file_callback)(const char *filename,
> +				     void *callback_data);

Align or make it one-liner:

typedef int (*igt_dir_file_callback)(const char *filename, void *callback_data);

Regards,
Kamil

> +
> +/**
> + * igt_dir_file_list_t: List of files with a relative path
> + * @relative_path: path to a file, relative to the root directory
> + * @match: a boolean used to filter the list of files. When match=true the
> + *	   file is processed, otherwise it is skipped
> + * @link: list head for linking files in the list
> + */
> +typedef struct {
> +	char *relative_path;
> +	bool match;
> +	struct igt_list_head link;
> +} igt_dir_file_list_t;
> +
> +/**
> + * igt_dir_t: Main struct for igt_dir
> + * @dirfd: file descriptor of the root directory
> + * @root_path: string of the root path, for example:
> + *	       /sys/kernel/debug/dri/0000:00:02.0/
> + * @file_list_head: head of the list of files
> + * @callback: Callback function for file operations. If NULL, defaults
> + *	      to reading and discarding file contents
> + */
> +typedef struct {
> +	int dirfd;
> +	char *root_path;
> +	struct igt_list_head file_list_head;
> +	igt_dir_file_callback callback;
> +} igt_dir_t;
> +
> +int igt_dir_get_fd_path(int fd, char *path, size_t path_len);
> +int igt_dir_callback_read_discard(const char *filename,
> +				  void *callback_data);
> +igt_dir_t *igt_dir_create(int dirfd);
> +int igt_dir_scan_dirfd(igt_dir_t *config, int scan_maxdepth);
> +int igt_dir_process_files(igt_dir_t *config,
> +			  igt_dir_file_callback callback,
> +			  void *callback_data);
> +void igt_dir_destroy(igt_dir_t *config);
> +int igt_dir_process_files_simple(int dirfd);
> +#endif /* IGT_DIR_H */
> diff --git a/lib/meson.build b/lib/meson.build
> index 1fed74565..7b3674c98 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -92,6 +92,7 @@ lib_sources = [
>  	'igt_kms.c',
>  	'igt_fb.c',
>  	'igt_core.c',
> +	'igt_dir.c',
>  	'igt_draw.c',
>  	'igt_list.c',
>  	'igt_map.c',
> -- 
> 2.43.0
> 


More information about the igt-dev mailing list