diff mbox series

[1/2] generic: Test readdir of modified directrory

Message ID 20210421092317.68716-2-amir73il@gmail.com (mailing list archive)
State New
Headers show
Series Test overlayfs readdir cache | expand

Commit Message

Amir Goldstein April 21, 2021, 9:23 a.m. UTC
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
diff mbox series

Patch

diff --git a/src/t_dir_offset2.c b/src/t_dir_offset2.c
index 5a6d7193..84dab9cf 100644
--- a/src/t_dir_offset2.c
+++ b/src/t_dir_offset2.c
@@ -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);
 }
diff --git a/tests/generic/700 b/tests/generic/700
new file mode 100755
index 00000000..eb08958d
--- /dev/null
+++ b/tests/generic/700
@@ -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
diff --git a/tests/generic/700.out b/tests/generic/700.out
new file mode 100644
index 00000000..f9acaa71
--- /dev/null
+++ b/tests/generic/700.out
@@ -0,0 +1,2 @@ 
+QA output created by 700
+Silence is golden
diff --git a/tests/generic/group b/tests/generic/group
index ab00cc04..bac5aa48 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -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