2 ms·
here is trivial program to dump dents to stdout, suitable for shell pipelines. example usage `./getdents64 . | xargs -0 printf "%q\n"` #define _GNU_SOURCE
by zokier 2y ago
here is trivial program to dump dents to stdout, suitable for shell pipelines. example usage `./getdents64 . | xargs -0 printf "%q\n"`
#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUF_SIZE 32768
struct linux_dirent64 {
ino64_t d_ino; /* 64-bit inode number */
off64_t d_off; /* Not an offset; see getdents() */
unsigned short d_reclen; /* Size of this dirent */
unsigned char d_type; /* File type */
char d_name[]; /* Filename (null-terminated) */
};
int writeall(char *buf, size_t len) {
ssize_t wres = 0;
wres = write(1, buf, len);
if (wres == -1) {
perror("write");
return -1;
}
if (((size_t)wres) < len) {
return writeall(buf + wres, len - wres);
}
return 0;
}
int main(int argc, char **argv) {
if (argc != 2) {
return EXIT_FAILURE;
}
int fd = open(argv[1], O_DIRECTORY | O_RDONLY);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
void *buf = malloc(BUF_SIZE);
ssize_t res = 0;
do {
res = getdents64(fd, buf, BUF_SIZE);
if (res == -1) {
perror("getdents64");
return EXIT_FAILURE;
}
void *it = buf;
while (it < (buf + res)) {
struct linux_dirent64 *elem = it;
it += elem->d_reclen;
size_t len = strlen(elem->d_name);
if (writeall(elem->d_name, len + 1) == -1) {
return EXIT_FAILURE;
}
}
} while (res > 0);
return EXIT_SUCCESS;
}
- db48x 2y agoYou’re still doing unnecessary work. You’re turning a list of files into a string, then parsing the string back into words. Your shell already provides a nice abstraction over calling readdir directly. A glob gives you a list, with no intermediate stage as a string that needs to be parsed. You can iterate directly over that list. Every language provides either direct access to the C library, so that you can call readdir, or it provides some abstraction over it to make the process less annoying. In Common Lisp the function `directory` takes a pathname and returns a list of pathnames for the files in the named directory. In Rust there is the `std::fs::read_dir` that gives you an iterator that yields `io::Result<std::fs::DirEntry>`, allowing easy handling of io errors and also neatly avoiding an extra allocation. Raku has a function `dir` that returns a similar iterator, but with the added feature that it can match the names against a regex for you and only yield the matches. You can fill in more examples from your favorite languages if you want.
- kazinator 2y agoThere is a glob() function you can use in POSIX C also to get an array of strings. The getdents system call being used in the above program is the basis for implementing readdir. It doesn't return a string, but rather a buffer of multiple directory entries. The program isn't parsing a giant string; it is parsing out the directory entry structures, which are variable length and have a length field so the next one can be found. The program writes each name including the null terminator, so that the output is suitable for utilities which understand that.
- db48x 2y agoThe problem is the phrase “suitable for shell pipelines”. If you are in a shell, you should not be doing anything like this. You should use a glob directly in the shell. You should not be calling an external program, having that program print out something, and then parsing it. Just use a glob right there in your shell script. If you do anything else, you are doing it wrong. Do I really have to say this again?