#ifndef __FreeBSD__ #define _DEFAULT_SOURCE #endif #include #include #include #include #include #include #include #include #include #include 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); }