330 lines
8.5 KiB
C
330 lines
8.5 KiB
C
#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);
|
|
}
|