virtio/fs/macos: use opendir instead of fdopendir and abstract DirStream#542
virtio/fs/macos: use opendir instead of fdopendir and abstract DirStream#542slp wants to merge 2 commits intocontainers:mainfrom
Conversation
fdopendir seems to be bogus on macOS, as a succession of fdopendir() -> readdir() -> closedir() -> fdopendir() leads to a DirStream that always return null on readdir(). This confused the guest into missing some files when reading a directory. Let's replace fdopendir() with opendir() which appears to work fine. This also fixes the issue that we were calling fdopendir() with HandleData->File's fd, with the former taking ownership of it and closing it on closedir(). This could cause an error like this when building the library with debug-assertions: fatal runtime error: IO Safety violation: owned file descriptor already closed, aborting Fixes: containers#539 Fixes: containers#541 Signed-off-by: Sergio Lopez <slp@redhat.com>
Abstract DirStream properties and operations to be managed in a cleaner and more idiomatic way. Signed-off-by: Sergio Lopez <slp@redhat.com>
|
It works great, all I'm a little bit curious about the |
The |
|
I did some more digging, I reverted back to your previous commit (with dup() call) and I found this: diff --git a/src/devices/src/virtio/fs/macos/passthrough.rs b/src/devices/src/virtio/fs/macos/passthrough.rs
index c1e385d..1a90d15 100644
--- a/src/devices/src/virtio/fs/macos/passthrough.rs
+++ b/src/devices/src/virtio/fs/macos/passthrough.rs
@@ -666,12 +666,13 @@ impl PassthroughFs {
let dir = unsafe { libc::fdopendir(newfd) };
if dir.is_null() {
let err = io::Error::last_os_error();
let _ = unsafe { libc::close(newfd) };
return Err(linux_error(err));
}
+ unsafe { libc::rewinddir(dir) };
ds.stream = dir as u64;
dir
} else {
ds.stream as *mut libc::DIR
};Just adding a |
I think you're right, good point. We were using Let me rework the first commit adding you as co-author. |
|
Ah, you are right, it seems there are 2 layers of offsets here, So instead of rewinddir we could also do something like this: diff --git a/src/devices/src/virtio/fs/macos/passthrough.rs b/src/devices/src/virtio/fs/macos/passthrough.rs
index 1a90d15..3fe7265 100644
--- a/src/devices/src/virtio/fs/macos/passthrough.rs
+++ b/src/devices/src/virtio/fs/macos/passthrough.rs
@@ -663,13 +663,14 @@ impl PassthroughFs {
if newfd < 0 {
return Err(linux_error(io::Error::last_os_error()));
}
+ unsafe { libc::lseek(newfd, 0, libc::SEEK_SET) };
let dir = unsafe { libc::fdopendir(newfd) };
if dir.is_null() {
let err = io::Error::last_os_error();
let _ = unsafe { libc::close(newfd) };
return Err(linux_error(err));
}
- unsafe { libc::rewinddir(dir) };
+ // unsafe { libc::rewinddir(dir) };
ds.stream = dir as u64;
dir
} else {I tested it on my machine and it seems to work as well. |
|
@pftbest I'd like to add a Co-authored-by line to one the commits. Could you please give me a name + email to attribute the commit to you? |
|
@slp Sure it's I also did some perf testing and it seems this version with opendir is slightly faster than all alternatives.
So the point seems moot, we can just merge this PR. Sorry I caused you some extra work. |
|
On a second thought, we shouldn't need to rewind nor lseek here, since this should be a fresh handle (with a fresh fd) created on To make things even weirder, I'm unable to reproduce the issue today. Could you please tell me which version of |
|
My |
Yes, I was referring to the |
|
I think this is expected behavior of |
Yes, but my point is that we should always be using |
|
@slp Here is a small test program that shows this issue. You can run it both on Linux and macOS and they behave exactly the same: Details
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define TEST_DIR "/usr"
static int count_entries(DIR *dir) {
struct dirent *ent;
int count = 0;
while ((ent = readdir(dir)) != NULL) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
printf("found: %s\n", ent->d_name);
count++;
}
return count;
}
int main(int argc, char *argv[])
{
int base_fd = open(TEST_DIR, O_RDONLY | O_DIRECTORY);
if (base_fd < 0) {
perror("open base_fd");
return 1;
}
int total1 = 0;
int total2 = 0;
{
int newfd = dup(base_fd);
if (newfd < 0) {
perror("dup1");
close(base_fd);
return 1;
}
DIR *dir = fdopendir(newfd);
if (!dir) {
perror("fdopendir1");
close(newfd);
return 1;
}
total1 = count_entries(dir);
printf("Total1: %d\n", total1);
closedir(dir);
}
{
int newfd = dup(base_fd);
if (newfd < 0) {
perror("dup2");
close(base_fd);
return 1;
}
DIR *dir = fdopendir(newfd);
if (!dir) {
perror("fdopendir2");
close(newfd);
return 1;
}
total2 = count_entries(dir);
printf("Total2: %d\n", total2);
closedir(dir);
}
if (total1 != total2) {
printf("\nTotal1 != Total2! %d != %d\n", total1, total2);
}
close(base_fd);
} |
|
@slp I think I got it. In function libkrun/src/devices/src/virtio/fs/macos/passthrough.rs Lines 545 to 554 in d4c0e9e This fd is then duped again and passed to However calling |
|
Okay, now it's clearer:
We need to fix the issue in #513 first. Then, between using opendir or fdopendir, we should favor the latter, as it allows the guest to read the contents of an unlinked directory (weird, but semantically correct). I'll resurrect #540 and extend it with the fix for |
|
Closing this one in favor of #544 |
fdopendir seems to be bogus on macOS, as a succession of fdopendir() -> readdir() -> closedir() -> fdopendir() leads to a DirStream that always return null on readdir(). This confused the guest into missing some files when reading a directory.
Let's replace fdopendir() with opendir() which appears to work fine.
This also fixes the issue that we were calling fdopendir() with HandleData->File's fd, with the former taking ownership of it and closing it on closedir(). This could cause an error like this when building the library with debug-assertions:
fatal runtime error: IO Safety violation: owned file descriptor already closed, aborting
Fixes: #539
Fixes: #541