mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-26 00:30:05 +01:00
util: add a helper to find files in a set of directories
Returns a number of paths for files with a given suffix in a priority-sorted list of directories. Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1204>
This commit is contained in:
parent
95a417cefa
commit
efea8463fe
4 changed files with 252 additions and 0 deletions
|
|
@ -307,6 +307,7 @@ foreach h: util_headers
|
|||
endforeach
|
||||
|
||||
src_libinput_util = [
|
||||
'src/util-files.c',
|
||||
'src/util-list.c',
|
||||
'src/util-ratelimit.c',
|
||||
'src/util-strings.c',
|
||||
|
|
|
|||
139
src/util-files.c
Normal file
139
src/util-files.c
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright © 2024 Red Hat, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "libinput-versionsort.h"
|
||||
#include "util-macros.h"
|
||||
#include "util-files.h"
|
||||
#include "util-strings.h"
|
||||
#include "util-list.h"
|
||||
|
||||
struct file {
|
||||
struct list link;
|
||||
char *name;
|
||||
char *directory;
|
||||
};
|
||||
|
||||
static void
|
||||
file_destroy(struct file *f)
|
||||
{
|
||||
list_remove(&f->link);
|
||||
free(f->name);
|
||||
free(f->directory);
|
||||
free(f);
|
||||
}
|
||||
|
||||
DEFINE_DESTROY_CLEANUP_FUNC(file);
|
||||
|
||||
/**
|
||||
* Appends to the given list all files files in the given directory that end
|
||||
* with the given with the given suffix.
|
||||
*/
|
||||
static void
|
||||
filenames(const char *directory, const char *suffix, struct list *list)
|
||||
{
|
||||
_autofree_ struct dirent **namelist = NULL;
|
||||
|
||||
int ndev = scandir(directory, &namelist, NULL, versionsort);
|
||||
if (ndev <= 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ndev; i++) {
|
||||
_autofree_ struct dirent *entry = namelist[i];
|
||||
if (!strendswith(entry->d_name, suffix))
|
||||
continue;
|
||||
|
||||
struct file *f = zalloc(sizeof(*f));
|
||||
f->name = safe_strdup(entry->d_name);
|
||||
f->directory = safe_strdup(directory);
|
||||
list_append(list, &f->link);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
filenamesort(const void *a, const void *b)
|
||||
{
|
||||
const struct file *f1 = *(const struct file **)a;
|
||||
const struct file *f2 = *(const struct file **)b;
|
||||
|
||||
return strverscmp(f1->name, f2->name);
|
||||
}
|
||||
|
||||
char **
|
||||
list_files(const char **directories,
|
||||
const char *suffix,
|
||||
size_t *nfiles_out)
|
||||
{
|
||||
struct list files = LIST_INIT(files);
|
||||
|
||||
const char **d = directories;
|
||||
while (*d) {
|
||||
struct list new_files = LIST_INIT(new_files);
|
||||
filenames(*d, suffix, &new_files);
|
||||
|
||||
struct file *old_file;
|
||||
list_for_each_safe(old_file, &files, link) {
|
||||
struct file *new_file;
|
||||
list_for_each_safe(new_file, &new_files, link) {
|
||||
if (streq(old_file->name, new_file->name)) {
|
||||
file_destroy(new_file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct file *new_file;
|
||||
list_for_each_safe(new_file, &new_files, link) {
|
||||
list_remove(&new_file->link);
|
||||
list_append(&files, &new_file->link);
|
||||
}
|
||||
d++;
|
||||
}
|
||||
|
||||
size_t nfiles = 0;
|
||||
struct file *f;
|
||||
list_for_each(f, &files, link) {
|
||||
nfiles++;
|
||||
}
|
||||
/* Allocating +1 conveniently handles the directories[0] = NULL case */
|
||||
_autofree_ struct file **fs = zalloc((nfiles + 1) * sizeof(*fs));
|
||||
size_t idx = 0;
|
||||
list_for_each_safe(f, &files, link) {
|
||||
fs[idx++] = f;
|
||||
list_remove(&f->link);
|
||||
list_init(&f->link); // So we can file_destroy it later
|
||||
}
|
||||
|
||||
qsort(fs, nfiles, sizeof(*fs), filenamesort);
|
||||
|
||||
char **paths = zalloc((nfiles + 1) * sizeof(*paths));
|
||||
for (size_t i = 0; i < nfiles; i++) {
|
||||
_destroy_(file) *f = fs[i];
|
||||
paths[i] = strdup_printf("%s/%s", f->directory, f->name);
|
||||
}
|
||||
|
||||
if (nfiles_out)
|
||||
*nfiles_out = nfiles;
|
||||
|
||||
return steal(&paths);
|
||||
}
|
||||
|
|
@ -62,3 +62,21 @@ xclose(int *fd)
|
|||
*fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In the NULL-terminated list of directories
|
||||
* search for files with the given suffix and return
|
||||
* a filename-ordered NULL-terminated list of those
|
||||
* full paths.
|
||||
*
|
||||
* The directories are given in descending priority order.
|
||||
* Any file with a given filename shadows the same file
|
||||
* in another directory of lower sorting order.
|
||||
*
|
||||
* If nfiles is not NULL, it is set to the number of
|
||||
* files returned (not including the NULL terminator).
|
||||
*/
|
||||
char **
|
||||
list_files(const char **directories,
|
||||
const char *suffix,
|
||||
size_t *nfiles);
|
||||
|
|
|
|||
|
|
@ -75,6 +75,99 @@ START_TEST(mkdir_p_test)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(find_files_test)
|
||||
{
|
||||
_autofree_ char *dirname = strdup("/tmp/litest_find_files_test.XXXXXX");
|
||||
mkdtemp(dirname);
|
||||
|
||||
_autofree_ char *d1 = strdup_printf("%s/d1", dirname);
|
||||
_autofree_ char *d2 = strdup_printf("%s/d2", dirname);
|
||||
_autofree_ char *d3 = strdup_printf("%s/d3", dirname);
|
||||
|
||||
litest_assert_neg_errno_success(mkdir_p(d1));
|
||||
litest_assert_neg_errno_success(mkdir_p(d2));
|
||||
litest_assert_neg_errno_success(mkdir_p(d3));
|
||||
|
||||
struct f {
|
||||
const char *name;
|
||||
const char *dir1;
|
||||
const char *dir2;
|
||||
const char *dir3;
|
||||
char *expected;
|
||||
} files[] = {
|
||||
{ "10-abc.suf", d1, d2, d3 },
|
||||
{ "20-def.suf", d1, NULL, d3 },
|
||||
{ "30-ghi.suf", d1, d2, NULL },
|
||||
{ "40-jkl.suf", NULL, d2, NULL },
|
||||
{ "50-mno.suf", NULL, d2, d3 },
|
||||
{ "60-pgr.suf", NULL, NULL, d3 },
|
||||
{ "70-abc.suf", NULL, NULL, d3 },
|
||||
{ "21-xyz.fix", NULL, NULL, d3 },
|
||||
{ "35-uvw.fix", NULL, d2, d3 },
|
||||
{ "70-rst.fix", d1, NULL, d3 },
|
||||
{ NULL },
|
||||
};
|
||||
for (struct f *f = files; f->name; f++) {
|
||||
if (f->dir1) {
|
||||
_autofree_ char *path = strdup_printf("%s/%s", f->dir1, f->name);
|
||||
close(open(path, O_WRONLY | O_CREAT, 0644));
|
||||
f->expected = steal(&path);
|
||||
}
|
||||
if (f->dir2) {
|
||||
_autofree_ char *path = strdup_printf("%s/%s", f->dir2, f->name);
|
||||
close(open(path, O_WRONLY | O_CREAT, 0644));
|
||||
if (!f->expected)
|
||||
f->expected = steal(&path);
|
||||
}
|
||||
if (f->dir3) {
|
||||
_autofree_ char *path = strdup_printf("%s/%s", f->dir3, f->name);
|
||||
close(open(path, O_WRONLY | O_CREAT, 0644));
|
||||
if (!f->expected)
|
||||
f->expected = steal(&path);
|
||||
}
|
||||
}
|
||||
|
||||
const char *dirs[] = {d1, d2, d3, NULL};
|
||||
size_t nfiles;
|
||||
_autostrvfree_ char **paths = list_files(dirs, "suf", &nfiles);
|
||||
litest_assert_int_eq(nfiles, (size_t)7);
|
||||
litest_assert_str_eq(paths[0], files[0].expected);
|
||||
litest_assert_str_eq(paths[1], files[1].expected);
|
||||
litest_assert_str_eq(paths[2], files[2].expected);
|
||||
litest_assert_str_eq(paths[3], files[3].expected);
|
||||
litest_assert_str_eq(paths[4], files[4].expected);
|
||||
litest_assert_str_eq(paths[5], files[5].expected);
|
||||
litest_assert_str_eq(paths[6], files[6].expected);
|
||||
litest_assert_str_eq(paths[7], NULL);
|
||||
|
||||
for (struct f *f = files; f->name; f++) {
|
||||
if (f->dir1) {
|
||||
_autofree_ char *path = strdup_printf("%s/%s", f->dir1, f->name);
|
||||
unlink(path);
|
||||
}
|
||||
if (f->dir2) {
|
||||
_autofree_ char *path = strdup_printf("%s/%s", f->dir2, f->name);
|
||||
unlink(path);
|
||||
}
|
||||
if (f->dir3) {
|
||||
_autofree_ char *path = strdup_printf("%s/%s", f->dir3, f->name);
|
||||
unlink(path);
|
||||
}
|
||||
free(f->expected);
|
||||
}
|
||||
rmdir(d1);
|
||||
rmdir(d2);
|
||||
rmdir(d3);
|
||||
rmdir(dirname);
|
||||
|
||||
const char *empty[] = {NULL};
|
||||
_autostrvfree_ char** empty_path = list_files(empty, "suf", &nfiles);
|
||||
litest_assert_int_eq(nfiles, (size_t)0);
|
||||
litest_assert_ptr_notnull(empty_path);
|
||||
litest_assert_ptr_null(empty_path[0]);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(array_for_each)
|
||||
{
|
||||
int ai[6];
|
||||
|
|
@ -2313,6 +2406,7 @@ int main(void)
|
|||
|
||||
ADD_TEST(auto_test);
|
||||
ADD_TEST(mkdir_p_test);
|
||||
ADD_TEST(find_files_test);
|
||||
|
||||
ADD_TEST(array_for_each);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue