rustix icon indicating copy to clipboard operation
rustix copied to clipboard

Consider adding a Dir::into_fd() method

Open linkmauve opened this issue 7 months ago • 1 comments

Currently, once an OwnedFd has been passed to rustix::fs::Dir, there is no way to reuse it.

My usecase is to call rustix::fs::openat() and rustix::fs::statat() on it after I’m done with the iteration (or even during, I don’t think there is any issue with that since getdents64(2) is stateless).

My current solution is to call rustix::io::dup() on the fd before creating the Dir, but that shouldn’t be needed and wastes one fd. Another solution would be to rustix::fs::open() it again once the iteration is done, but that’s wasteful too.

rustix::fs::Dir::read_from has the same issue, it implements dup(2) manually by calling fcntl(4, F_GETFL) then openat(4, ".", …) with 4 being the fd being passed to it.

And since getdents64(2) is stateless, I believe there is no unsoundness issue with e.g. multiple rustix::fs::Dir being created around the same fd.

linkmauve avatar Apr 30 '25 17:04 linkmauve

The tricky thing here is that rustix is aiming to have the same API across both the libc and linux-raw backends, and in the libc backend, it uses opendir/readdir/closedir. libc doesn't have a way to free a DIR object without closing the fd.

One option might be to have the libc backend implement into_fd by calling dup before calling closedir, however I'm not sure if it's too awkward if into_fd() has to be fallible.

sunfishcode avatar Apr 30 '25 22:04 sunfishcode

Hello. So, my use case actually needs to reuse the dir file descriptor during iteration. This is because I need to remove a directory, and to do so I must recursively remove all its content. So the loop looks like:

for entry in dir.into_iter().flatten() {
    let name = entry.file_name().to_bytes();

    match name {
        b"." | b".." => continue,
        otherwise => {
            // use statat to determine if the entry is a directory
            // if it is, open it with openat and recursively delete it
           // if not, use unlinkat directly
        }
    }
}

So I need access to the file descriptor while I am iterating. Right now I am getting around it by turning the file descriptor into a raw file descriptor, then using unsafe code to create a BorrowedFd from the raw file descriptor. However, I am not sure how portable this is (if at all). It seems to work on the linux backend, but I imagine there may be some issue with the libcbackend, since the documentation for fdopendir states the application should not use the file descriptor after passing it to fdopendir.

I believe libc has a dirfd function that allows us to get the file descriptor from a DIR handle. Could we use that somehow? This would allow me to use a while let Some(entry) = dir.read() loop and then call dir.fd() internally in the loop to make all the *at syscalls. This would also trivially solve @linkmauve's problem as a bonus.

LGFae avatar Nov 14 '25 19:11 LGFae