[PATCH v3 resend i-g-t 1/6] lib/igt_dir: Directory processing and flexible file handling
Sokolowski, Jan
jan.sokolowski at intel.com
Tue Jun 24 13:24:00 UTC 2025
> -----Original Message-----
> From: igt-dev <igt-dev-bounces at lists.freedesktop.org> On Behalf Of Peter
> Senna Tschudin
> Sent: Monday, June 16, 2025 9:43 AM
> To: igt-dev at lists.freedesktop.org
> Cc: Peter Senna Tschudin <peter.senna at linux.intel.com>; Wajdeczko, Michal
> <Michal.Wajdeczko at intel.com>; Bernatowicz, Marcin
> <marcin.bernatowicz at intel.com>; kamil.konieczny at linux.intel.com;
> Piecielska, Katarzyna <katarzyna.piecielska at intel.com>; Kempczynski,
> Zbigniew <zbigniew.kempczynski at intel.com>; Musial, Ewelina
> <ewelina.musial at intel.com>
> Subject: [PATCH v3 resend i-g-t 1/6] lib/igt_dir: Directory processing and
> flexible file handling
>
> This update 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 from debugfs:
>
> 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);
>
> 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.
>
> Cc: michal.wajdeczko at intel.com
> Cc: marcin.bernatowicz at intel.com
> Cc: kamil.konieczny at linux.intel.com
> Cc: katarzyna.piecielska at intel.com
> Cc: zbigniew.kempczynski at intel.com
> Cc: ewelina.musial at intel.com
> Signed-off-by: Peter Senna Tschudin <peter.senna at linux.intel.com>
> ---
> v3:
> - unchanged from v2
> v2:
> - changed style of comparison to NULL
>
> lib/igt_dir.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++
> lib/igt_dir.h | 61 ++++++++++++
> lib/meson.build | 1 +
> 3 files changed, 322 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..8f5a25e35
> --- /dev/null
> +++ b/lib/igt_dir.c
> @@ -0,0 +1,260 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright © 2025 Intel Corporation
> + */
> +
> +#include <dirent.h>
> +#include <fcntl.h>
> +
> +#include "igt.h"
> +#include "lib/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';
> + 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;
> + }
> + 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;
> + }
> + buf[bytes_read] = '\0';
> + igt_debug("Read %zd bytes from file %s: %s\n", bytes_read,
> + filename, buf);
> + close(fd);
> + 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;
> +
> + 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);
> + 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,
> + void *callback_data)
> +{
> + 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 */
Personally, I think this comment is somewhat superfluous, something like i + 1; /* adds 1 */.
But it's a minor thing really imho.
> + 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) {
> + free(file_list_entry->relative_path);
> + free(file_list_entry);
> + }
> +
> + free(config->root_path);
> + free(config);
> +}
> diff --git a/lib/igt_dir.h b/lib/igt_dir.h
> new file mode 100644
> index 000000000..fb9230862
> --- /dev/null
> +++ b/lib/igt_dir.h
> @@ -0,0 +1,61 @@
> +/* 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);
> +
> +/**
> + * 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);
> +#endif /* IGT_DIR_H */
> diff --git a/lib/meson.build b/lib/meson.build
> index ff81baae1..ec4a71bd7 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -91,6 +91,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
Reviewed-by: Jan Sokolowski <jan.sokolowski at intel.com>
More information about the igt-dev
mailing list