summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJamie Liu <jamieliu@google.com>2020-07-30 15:00:42 -0700
committergVisor bot <gvisor-bot@google.com>2020-07-30 15:02:22 -0700
commit1b11326ecdd788fccc8e396b7467bbbb591d563f (patch)
tree45337adf1d16ed9028567aac1ed2f6d5b560d791
parentf15d5a8d0fe4ad7d072909e7957977756f7be118 (diff)
Call lseek(0, SEEK_CUR) unconditionally in runsc fsgofer's Readdir(offset=0).
9P2000.L is silent as to how readdir RPCs interact with directory mutation. The most performant option is for Treaddir with offset=0 to restart iteration, avoiding needing to walk+open+clunk a new directory fid between invocations of getdents64(2), and the VFS2 gofer client assumes this is the case. Make this actually true for the runsc fsgofer. Fixes #3344, #3345, #3355 PiperOrigin-RevId: 324090384
-rw-r--r--images/basic/hostoverlaytest/Dockerfile3
-rw-r--r--images/basic/hostoverlaytest/copy_up_testfile.txt (renamed from images/basic/hostoverlaytest/testfile.txt)0
-rw-r--r--images/basic/hostoverlaytest/test_copy_up.c (renamed from images/basic/hostoverlaytest/test.c)2
-rw-r--r--images/basic/hostoverlaytest/test_rewinddir.c78
-rw-r--r--runsc/fsgofer/fsgofer.go9
-rw-r--r--test/e2e/integration_test.go28
6 files changed, 114 insertions, 6 deletions
diff --git a/images/basic/hostoverlaytest/Dockerfile b/images/basic/hostoverlaytest/Dockerfile
index d83439e9c..6cef1a542 100644
--- a/images/basic/hostoverlaytest/Dockerfile
+++ b/images/basic/hostoverlaytest/Dockerfile
@@ -4,4 +4,5 @@ WORKDIR /root
COPY . .
RUN apt-get update && apt-get install -y gcc
-RUN gcc -O2 -o test test.c
+RUN gcc -O2 -o test_copy_up test_copy_up.c
+RUN gcc -O2 -o test_rewinddir test_rewinddir.c
diff --git a/images/basic/hostoverlaytest/testfile.txt b/images/basic/hostoverlaytest/copy_up_testfile.txt
index e4188c841..e4188c841 100644
--- a/images/basic/hostoverlaytest/testfile.txt
+++ b/images/basic/hostoverlaytest/copy_up_testfile.txt
diff --git a/images/basic/hostoverlaytest/test.c b/images/basic/hostoverlaytest/test_copy_up.c
index 088f90746..010b261dc 100644
--- a/images/basic/hostoverlaytest/test.c
+++ b/images/basic/hostoverlaytest/test_copy_up.c
@@ -6,7 +6,7 @@
#include <unistd.h>
int main(int argc, char** argv) {
- const char kTestFilePath[] = "testfile.txt";
+ const char kTestFilePath[] = "copy_up_testfile.txt";
const char kOldFileData[] = "old data\n";
const char kNewFileData[] = "new data\n";
const size_t kPageSize = sysconf(_SC_PAGE_SIZE);
diff --git a/images/basic/hostoverlaytest/test_rewinddir.c b/images/basic/hostoverlaytest/test_rewinddir.c
new file mode 100644
index 000000000..f1a4085e1
--- /dev/null
+++ b/images/basic/hostoverlaytest/test_rewinddir.c
@@ -0,0 +1,78 @@
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+int main(int argc, char** argv) {
+ const char kDirPath[] = "rewinddir_test_dir";
+ const char kFileBasename[] = "rewinddir_test_file";
+
+ // Create the test directory.
+ if (mkdir(kDirPath, 0755) < 0) {
+ err(1, "mkdir(%s)", kDirPath);
+ }
+
+ // The test directory should initially be empty.
+ DIR* dir = opendir(kDirPath);
+ if (!dir) {
+ err(1, "opendir(%s)", kDirPath);
+ }
+ int failed = 0;
+ while (1) {
+ errno = 0;
+ struct dirent* d = readdir(dir);
+ if (!d) {
+ if (errno != 0) {
+ err(1, "readdir");
+ }
+ break;
+ }
+ if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
+ warnx("unexpected file %s in new directory", d->d_name);
+ failed = 1;
+ }
+ }
+
+ // Create a file in the test directory.
+ char* file_path = malloc(strlen(kDirPath) + 1 + strlen(kFileBasename));
+ if (!file_path) {
+ errx(1, "malloc");
+ }
+ strcpy(file_path, kDirPath);
+ file_path[strlen(kDirPath)] = '/';
+ strcpy(file_path + strlen(kDirPath) + 1, kFileBasename);
+ if (mknod(file_path, 0644, 0) < 0) {
+ err(1, "mknod(%s)", file_path);
+ }
+
+ // After rewinddir(), re-reading the directory stream should yield the new
+ // file.
+ rewinddir(dir);
+ size_t found_file = 0;
+ while (1) {
+ errno = 0;
+ struct dirent* d = readdir(dir);
+ if (!d) {
+ if (errno != 0) {
+ err(1, "readdir");
+ }
+ break;
+ }
+ if (strcmp(d->d_name, kFileBasename) == 0) {
+ found_file++;
+ } else if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
+ warnx("unexpected file %s in new directory", d->d_name);
+ failed = 1;
+ }
+ }
+ if (found_file != 1) {
+ warnx("readdir returned file %s %zu times, wanted 1", kFileBasename,
+ found_file);
+ failed = 1;
+ }
+
+ return failed;
+}
diff --git a/runsc/fsgofer/fsgofer.go b/runsc/fsgofer/fsgofer.go
index ebefeacf2..c6694c278 100644
--- a/runsc/fsgofer/fsgofer.go
+++ b/runsc/fsgofer/fsgofer.go
@@ -979,9 +979,12 @@ func (l *localFile) Readdir(offset uint64, count uint32) ([]p9.Dirent, error) {
skip := uint64(0)
- // Check if the file is at the correct position already. If not, seek to the
- // beginning and read the entire directory again.
- if l.lastDirentOffset != offset {
+ // Check if the file is at the correct position already. If not, seek to
+ // the beginning and read the entire directory again. We always seek if
+ // offset is 0, since this is side-effectual (equivalent to rewinddir(3),
+ // which causes the directory stream to resynchronize with the directory's
+ // current contents).
+ if l.lastDirentOffset != offset || offset == 0 {
if _, err := syscall.Seek(l.file.FD(), 0, 0); err != nil {
return nil, extractErrno(err)
}
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go
index 5465dee9b..6fe6d304f 100644
--- a/test/e2e/integration_test.go
+++ b/test/e2e/integration_test.go
@@ -434,7 +434,33 @@ func TestHostOverlayfsCopyUp(t *testing.T) {
if got, err := d.Run(ctx, dockerutil.RunOpts{
Image: "basic/hostoverlaytest",
WorkDir: "/root",
- }, "./test"); err != nil {
+ }, "./test_copy_up"); err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ } else if got != "" {
+ t.Errorf("test failed:\n%s", got)
+ }
+}
+
+// TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory
+// stream to refer to the current state of the corresponding directory, as a
+// call to opendir() would have done" as required by POSIX, when the directory
+// in question is host overlayfs.
+//
+// This test specifically targets host overlayfs because, per POSIX, "if a file
+// is removed from or added to the directory after the most recent call to
+// opendir() or rewinddir(), whether a subsequent call to readdir() returns an
+// entry for that file is unspecified"; the host filesystems used by other
+// automated tests yield newly-added files from readdir() even if the fsgofer
+// does not explicitly rewinddir(), but overlayfs does not.
+func TestHostOverlayfsRewindDir(t *testing.T) {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if got, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/hostoverlaytest",
+ WorkDir: "/root",
+ }, "./test_rewinddir"); err != nil {
t.Fatalf("docker run failed: %v", err)
} else if got != "" {
t.Errorf("test failed:\n%s", got)