@@ -13,6 +13,7 @@
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@@ -26,19 +27,28 @@ struct linux_dirent64 {
#define BUF_SIZE 4096
#define HISTORY_LEN 1024
+#define NAME_SIZE 10
static uint64_t d_off_history[HISTORY_LEN];
static uint64_t d_ino_history[HISTORY_LEN];
+static char d_name_history[HISTORY_LEN][NAME_SIZE+1];
+
+static int is_dot_or_dot_dot(const char *name)
+{
+ return !strcmp(name, ".") || !strcmp(name, "..");
+}
int
main(int argc, char *argv[])
{
- int fd, nread;
+ int fd, nread, nread_0 = 0;
char buf[BUF_SIZE];
struct linux_dirent64 *d;
int bpos, total, i;
off_t lret;
int retval = EXIT_SUCCESS;
+ int create = 0, unlink = 0;
+ FILE *warn = stderr;
fd = open(argv[1], O_RDONLY | O_DIRECTORY);
if (fd < 0) {
@@ -46,6 +56,15 @@ main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
+ if (argc > 2) {
+ if (!strcmp(argv[2], "-u"))
+ unlink = 1;
+ else
+ create = 1;
+ /* Some mismatch warnings are expected */
+ warn = stdout;
+ }
+
total = 0;
for ( ; ; ) {
nread = syscall(SYS_getdents64, fd, buf, BUF_SIZE);
@@ -54,6 +73,10 @@ main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
+ if (total == 0 && (create || unlink)) {
+ nread_0 = nread;
+ printf("getdents at offset 0 returned %d bytes\n", nread);
+ }
if (nread == 0)
break;
@@ -75,20 +98,40 @@ main(int argc, char *argv[])
}
d_off_history[total] = d->d_off;
d_ino_history[total] = d->d_ino;
+ strncpy(d_name_history[total], d->d_name, NAME_SIZE);
bpos += d->d_reclen;
}
}
+ if (create) {
+ if (openat(fd, argv[2], O_CREAT, 0600) < 0) {
+ perror("openat");
+ exit(EXIT_FAILURE);
+ }
+ printf("created entry %d:%s\n", total, argv[2]);
+ }
+
/* check if seek works correctly */
d = (struct linux_dirent64 *)buf;
for (i = total - 1; i >= 0; i--)
{
+ const char *name = i > 0 ? d_name_history[i - 1] : NULL;
+
lret = lseek(fd, i > 0 ? d_off_history[i - 1] : 0, SEEK_SET);
if (lret == -1) {
perror("lseek");
exit(EXIT_FAILURE);
}
+ /* Unlink prev entry, but not "." nor ".." */
+ if (unlink && name && !is_dot_or_dot_dot(name)) {
+ if (unlinkat(fd, name, 0) < 0) {
+ perror("unlinkat");
+ exit(EXIT_FAILURE);
+ }
+ printf("unlinked entry %d:%s\n", i - 1, name);
+ }
+
nread = syscall(SYS_getdents64, fd, buf, BUF_SIZE);
if (nread == -1) {
perror("getdents");
@@ -96,17 +139,31 @@ main(int argc, char *argv[])
}
if (nread == 0) {
- fprintf(stderr, "getdents returned 0 on entry %d\n", i);
+ fprintf(warn, "getdents returned 0 on entry %d\n", i);
+ retval = EXIT_FAILURE;
+ }
+
+ if (i == 0 && nread_0 && nread != nread_0) {
+ fprintf(warn, "getdents at offset 0 returned %d bytes, expected %d\n", nread, nread_0);
retval = EXIT_FAILURE;
}
if (d->d_ino != d_ino_history[i]) {
- fprintf(stderr, "entry %d has inode %lld, expected %lld\n",
+ fprintf(warn, "entry %d has inode %lld, expected %lld\n",
i, (long long int)d->d_ino, (long long int)d_ino_history[i]);
retval = EXIT_FAILURE;
}
+ if (create || unlink)
+ printf("found entry %d:%s (nread=%d)\n", i, d->d_name, nread);
}
+ /*
+ * With create/unlink option we expect to find some mismatch with getdents
+ * before create/unlink, so if everything matches there is something wrong.
+ */
+ if (create || unlink)
+ retval = retval ? EXIT_SUCCESS : EXIT_FAILURE;
+
close(fd);
exit(retval);
}
new file mode 100755
@@ -0,0 +1,60 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 CTERA Networks. All Rights Reserved.
+#
+# Check that directory content read from an open dir fd reflects
+# changes to the directory after open.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+
+_cleanup()
+{
+ cd /
+ rm -f $tmp.*
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# real QA test starts here
+_supported_fs generic
+_require_test
+
+rm -f $seqres.full
+
+testdir=$TEST_DIR/test-$seq
+rm -rf $testdir
+mkdir $testdir
+
+# Use small number of files that we can assume to be returned in a single
+# gendents() call, so t_dir_offset2 can compare the entries count before
+# and after seek to start
+for n in {1..10}; do
+ touch $testdir/$n
+done
+
+# Does opendir, readdir, seek to offsets, readdir and
+# compares d_ino of entries on second readdir
+$here/src/t_dir_offset2 $testdir
+
+# Check readdir content changes after adding a new file before seek to start
+echo "Create file in an open dir:" >> $seqres.full
+$here/src/t_dir_offset2 $testdir 0 2>&1 >> $seqres.full || \
+ echo "Missing created file in open dir (see $seqres.full for details)"
+
+# Check readdir content changes after removing files before seek to start
+echo "Remove files in an open dir:" >> $seqres.full
+$here/src/t_dir_offset2 $testdir -u 2>&1 >> $seqres.full || \
+ echo "Found unlinked files in open dir (see $seqres.full for details)"
+
+# success, all done
+echo "Silence is golden"
+status=0
new file mode 100644
@@ -0,0 +1,2 @@
+QA output created by 700
+Silence is golden
@@ -634,3 +634,4 @@
629 auto quick rw copy_range
630 auto quick rw dedupe clone
631 auto rw overlay rename
+700 auto quick dir
Check that directory content read from an open dir fd reflects changes to the directory after open. Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- src/t_dir_offset2.c | 63 ++++++++++++++++++++++++++++++++++++++++--- tests/generic/700 | 60 +++++++++++++++++++++++++++++++++++++++++ tests/generic/700.out | 2 ++ tests/generic/group | 1 + 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100755 tests/generic/700 create mode 100644 tests/generic/700.out