This commit is contained in:
2026-08-18 09:20:44 +02:00
commit b826cd721a
522 changed files with 93730 additions and 0 deletions
+329
View File
@@ -0,0 +1,329 @@
#ifndef __FreeBSD__
#define _DEFAULT_SOURCE
#endif
#include <sys/types.h>
#include <dirent.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct observed_entry {
char *name;
off_t cookie;
unsigned char type;
};
struct observed_list {
struct observed_entry *entries;
size_t count;
size_t capacity;
};
static void
append_entry(struct observed_list *list, const char *name, off_t cookie,
unsigned char type)
{
struct observed_entry *entries;
if (list->count == list->capacity) {
list->capacity = list->capacity == 0 ? 64 : list->capacity * 2;
entries = realloc(list->entries,
list->capacity * sizeof(*list->entries));
if (entries == NULL)
err(1, "realloc entries");
list->entries = entries;
}
list->entries[list->count].name = strdup(name);
if (list->entries[list->count].name == NULL)
err(1, "strdup");
list->entries[list->count].cookie = cookie;
list->entries[list->count].type = type;
list->count++;
}
static uint64_t
entry_hash(const struct observed_list *list)
{
uint64_t hash;
hash = UINT64_C(14695981039346656037);
for (size_t index = 0; index < list->count; index++) {
for (const unsigned char *name =
(const unsigned char *)list->entries[index].name;
*name != '\0'; name++) {
hash ^= *name;
hash *= UINT64_C(1099511628211);
}
hash ^= 0;
hash *= UINT64_C(1099511628211);
hash ^= list->entries[index].type;
hash *= UINT64_C(1099511628211);
}
return (hash);
}
static void
print_type_counts(const struct observed_list *list)
{
size_t block, character, directory, fifo, link, other, regular, socket,
unknown;
block = character = directory = fifo = link = other = regular = socket =
unknown = 0;
for (size_t index = 0; index < list->count; index++) {
switch (list->entries[index].type) {
case DT_BLK:
block++;
break;
case DT_CHR:
character++;
break;
case DT_DIR:
directory++;
break;
case DT_FIFO:
fifo++;
break;
case DT_LNK:
link++;
break;
case DT_REG:
regular++;
break;
case DT_SOCK:
socket++;
break;
case DT_UNKNOWN:
unknown++;
break;
default:
other++;
break;
}
}
printf("types=dir:%zu,reg:%zu,lnk:%zu,chr:%zu,blk:%zu,fifo:%zu,"
"sock:%zu,unknown:%zu,other:%zu ", directory, regular, link,
character, block, fifo, socket, unknown, other);
}
static void
free_list(struct observed_list *list)
{
for (size_t index = 0; index < list->count; index++)
free(list->entries[index].name);
free(list->entries);
}
static void
check_unique_names(const struct observed_list *list)
{
for (size_t left = 0; left < list->count; left++) {
for (size_t right = left + 1; right < list->count; right++) {
if (strcmp(list->entries[left].name,
list->entries[right].name) == 0)
errx(1, "duplicate name: %s",
list->entries[left].name);
}
}
}
static struct observed_list
scan_getdirentries(const char *path, size_t buffer_size)
{
struct observed_list list = { 0 };
struct dirent *entry;
char *buffer, *end;
off_t base, previous;
ssize_t bytes;
int fd;
fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd < 0)
err(1, "open %s", path);
buffer = malloc(buffer_size);
if (buffer == NULL)
err(1, "malloc");
previous = 0;
for (;;) {
base = -1;
bytes = getdirentries(fd, buffer, buffer_size, &base);
if (bytes < 0)
err(1, "getdirentries %s", path);
if (bytes == 0)
break;
end = buffer + bytes;
for (entry = (struct dirent *)buffer; (char *)entry < end;
entry = (struct dirent *)((char *)entry + entry->d_reclen)) {
if (entry->d_reclen == 0 ||
(char *)entry + entry->d_reclen > end)
errx(1, "invalid dirent record");
if (entry->d_off <= previous)
errx(1, "non-increasing d_off %jd after %jd",
(intmax_t)entry->d_off, (intmax_t)previous);
append_entry(&list, entry->d_name, entry->d_off,
entry->d_type);
previous = entry->d_off;
}
}
free(buffer);
if (close(fd) != 0)
err(1, "close %s", path);
check_unique_names(&list);
return (list);
}
static void
verify_kernel_cookies(const char *path, size_t buffer_size,
const struct observed_list *list)
{
struct dirent *entry;
char *buffer;
off_t base;
ssize_t bytes;
int fd;
buffer = malloc(buffer_size);
if (buffer == NULL)
err(1, "malloc restart buffer");
for (size_t index = 0; index < list->count; index++) {
fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd < 0)
err(1, "open restart %s", path);
if (lseek(fd, list->entries[index].cookie, SEEK_SET) < 0)
err(1, "lseek cookie %jd",
(intmax_t)list->entries[index].cookie);
base = -1;
bytes = getdirentries(fd, buffer, buffer_size, &base);
if (bytes < 0)
err(1, "getdirentries restart");
if (index + 1 == list->count) {
if (bytes != 0)
errx(1, "final d_off cookie did not reach EOF");
} else {
if (bytes <= 0)
errx(1, "restart cookie returned no dirent");
entry = (struct dirent *)buffer;
if (entry->d_reclen == 0 || entry->d_reclen > (size_t)bytes)
errx(1, "restart cookie returned a truncated dirent");
if (strcmp(entry->d_name, list->entries[index + 1].name) != 0)
errx(1, "cookie %jd resumed at %s, expected %s",
(intmax_t)list->entries[index].cookie,
entry->d_name, list->entries[index + 1].name);
}
if (close(fd) != 0)
err(1, "close restart");
}
free(buffer);
}
static struct observed_list
scan_readdir(const char *path)
{
struct observed_list list = { 0 };
struct dirent *entry;
DIR *directory;
long cookie, previous;
directory = opendir(path);
if (directory == NULL)
err(1, "opendir %s", path);
previous = 0;
while ((entry = readdir(directory)) != NULL) {
cookie = telldir(directory);
if (cookie <= previous)
errx(1, "non-increasing telldir cookie %ld after %ld",
cookie, previous);
append_entry(&list, entry->d_name, (off_t)cookie,
entry->d_type);
previous = cookie;
}
if (closedir(directory) != 0)
err(1, "closedir %s", path);
check_unique_names(&list);
return (list);
}
static void
verify_seekdir(const char *path, const struct observed_list *list)
{
struct dirent *entry;
DIR *directory;
long cookie;
for (size_t index = 0; index < list->count; index++) {
directory = opendir(path);
if (directory == NULL)
err(1, "opendir restart %s", path);
for (size_t position = 0; position <= index; position++) {
entry = readdir(directory);
if (entry == NULL ||
strcmp(entry->d_name, list->entries[position].name) != 0)
errx(1, "seekdir setup differs at entry %zu",
position);
}
cookie = telldir(directory);
if ((off_t)cookie != list->entries[index].cookie)
errx(1, "telldir cookie differs at entry %zu", index);
seekdir(directory, cookie);
entry = readdir(directory);
if (index + 1 == list->count) {
if (entry != NULL)
errx(1, "final telldir cookie did not reach EOF");
} else if (entry == NULL ||
strcmp(entry->d_name, list->entries[index + 1].name) != 0) {
errx(1, "seekdir cookie %jd resumed at %s, expected %s",
(intmax_t)list->entries[index].cookie,
entry == NULL ? "EOF" : entry->d_name,
list->entries[index + 1].name);
}
if (closedir(directory) != 0)
err(1, "closedir restart");
}
}
int
main(int argc, char **argv)
{
struct observed_list kernel, libc;
char *end;
unsigned long raw_size;
size_t buffer_size;
if (argc < 2 || argc > 3)
errx(2, "usage: %s directory [buffer-size]", argv[0]);
buffer_size = 128;
if (argc == 3) {
raw_size = strtoul(argv[2], &end, 0);
if (*argv[2] == '\0' || *end != '\0' || raw_size < 64 ||
raw_size > 1024 * 1024)
errx(2, "invalid buffer size: %s", argv[2]);
buffer_size = (size_t)raw_size;
}
kernel = scan_getdirentries(argv[1], buffer_size);
verify_kernel_cookies(argv[1], buffer_size, &kernel);
libc = scan_readdir(argv[1]);
verify_seekdir(argv[1], &libc);
if (kernel.count != libc.count)
errx(1, "getdirentries count %zu differs from readdir count %zu",
kernel.count, libc.count);
for (size_t index = 0; index < kernel.count; index++) {
if (strcmp(kernel.entries[index].name, libc.entries[index].name) != 0)
errx(1, "API order differs at entry %zu", index);
if (kernel.entries[index].type != libc.entries[index].type)
errx(1, "API type differs at entry %zu", index);
}
print_type_counts(&kernel);
printf("entries=%zu d_off_restarts=%zu seekdir_restarts=%zu "
"buffer=%zu fnv1a64=%016" PRIx64 "\n", kernel.count,
kernel.count, libc.count, buffer_size, entry_hash(&kernel));
free_list(&libc);
free_list(&kernel);
return (0);
}