[PATCH] cifs/002: check nlink returned by fstat()

ChenXiaoSong <[email protected]>
Newsgroups org.kernel.vger.linux-cifs,org.kernel.vger.fstests
Message-ID <[email protected]>
From: ChenXiaoSong <[email protected]>

Add a CIFS test to verify that fstat(2) returns the expected
st_nlink value as hardlinks are created and removed.

Signed-off-by: ChenXiaoSong <[email protected]>
---
 src/Makefile       |   2 +-
 src/fstat.c        | 200 +++++++++++++++++++++++++++++++++++++++++++++
 tests/cifs/002     |  62 ++++++++++++++
 tests/cifs/002.out |   2 +
 4 files changed, 265 insertions(+), 1 deletion(-)
 create mode 100644 src/fstat.c
 create mode 100755 tests/cifs/002
 create mode 100644 tests/cifs/002.out

diff --git a/src/Makefile b/src/Makefile
index 31ac43b2..b0886279 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -6,7 +6,7 @@
 TOPDIR = ..
 include $(TOPDIR)/include/builddefs
 
-TARGETS = dirstress fill fill2 getpagesize holes lstat64 \
+TARGETS = dirstress fill fill2 getpagesize holes lstat64 fstat \
 	nametest permname randholes runas truncfile usemem \
 	mmapcat append_reader append_writer dirperf metaperf \
 	devzero feature alloc fault fstest t_access_root \
diff --git a/src/fstat.c b/src/fstat.c
new file mode 100644
index 00000000..59221186
--- /dev/null
+++ b/src/fstat.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 KylinSoft Co., Ltd. All rights reserved.
+ * Author(s): ChenXiaoSong <[email protected]>
+ *
+ *  from
+ *  src/lstat64.c
+ *  Copyright (c) 2000-2002 Silicon Graphics, Inc.
+ *  All Rights Reserved.
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+
+long	timebuf;
+
+void
+timesince(long timesec)
+{
+	long	d_since;	/* days */
+	long	h_since;	/* hours */
+	long	m_since;	/* minutes */
+	long	s_since;	/* seconds */
+
+	s_since = timebuf - timesec;
+	d_since = s_since / 86400l ;
+	s_since -= d_since * 86400l ;
+	h_since = s_since / 3600l ;
+	s_since -= h_since * 3600l ;
+	m_since = s_since / 60l ;
+	s_since -= m_since * 60l ;
+
+	printf("(%05ld.%02ld:%02ld:%02ld)\n",
+			d_since, h_since, m_since, s_since);
+}
+
+void
+usage(void)
+{
+	fprintf(stderr, "Usage: fstat [-t] filename ...\n");
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct stat	sbuf;
+	int		i, c;
+	int		terse_flag = 0;
+
+	while ((c = getopt(argc, argv, "t")) != EOF) {
+		switch (c) {
+			case 't':
+				terse_flag = 1;
+				break;
+
+			case '?':
+				usage();
+		}
+	}
+	if (optind == argc) {
+		usage();
+	}
+
+	time(&timebuf);
+
+	for (i = optind; i < argc; i++) {
+		char mode[] = "----------";
+		int fd;
+
+		fd = open(argv[i], O_RDONLY | O_NONBLOCK);
+		if (fd < 0) {
+			perror(argv[i]);
+			continue;
+		}
+
+		if (fstat(fd, &sbuf) < 0) {
+			perror(argv[i]);
+			close(fd);
+			continue;
+		}
+
+		if (terse_flag) {
+			printf("%s %llu ", argv[i], (unsigned long long)sbuf.st_size);
+		}
+		else {
+			printf("  File: \"%s\"\n", argv[i]);
+			printf("  Size: %-10llu", (unsigned long long)sbuf.st_size);
+		}
+
+		if (sbuf.st_mode & S_IXOTH)
+			mode[9] = 'x';
+		if (sbuf.st_mode & S_IWOTH)
+			mode[8] = 'w';
+		if (sbuf.st_mode & S_IROTH)
+			mode[7] = 'r';
+		if (sbuf.st_mode & S_IXGRP)
+			mode[6] = 'x';
+		if (sbuf.st_mode & S_IWGRP)
+			mode[5] = 'w';
+		if (sbuf.st_mode & S_IRGRP)
+			mode[4] = 'r';
+		if (sbuf.st_mode & S_IXUSR)
+			mode[3] = 'x';
+		if (sbuf.st_mode & S_IWUSR)
+			mode[2] = 'w';
+		if (sbuf.st_mode & S_IRUSR)
+			mode[1] = 'r';
+		if (sbuf.st_mode & S_ISVTX)
+			mode[9] = 't';
+		if (sbuf.st_mode & S_ISGID)
+			mode[6] = 's';
+		if (sbuf.st_mode & S_ISUID)
+			mode[3] = 's';
+
+		if (!terse_flag)
+			printf("   Filetype: ");
+		switch (sbuf.st_mode & S_IFMT) {
+		case S_IFSOCK:
+			if (!terse_flag)
+				puts("Socket");
+			mode[0] = 's';
+			break;
+		case S_IFDIR:
+			if (!terse_flag)
+				puts("Directory");
+			mode[0] = 'd';
+			break;
+		case S_IFCHR:
+			if (!terse_flag)
+				puts("Character Device");
+			mode[0] = 'c';
+			break;
+		case S_IFBLK:
+			if (!terse_flag)
+				puts("Block Device");
+			mode[0] = 'b';
+			break;
+		case S_IFREG:
+			if (!terse_flag)
+				puts("Regular File");
+			mode[0] = '-';
+			break;
+		case S_IFLNK:
+			if (!terse_flag)
+				puts("Symbolic Link");
+			mode[0] = 'l';
+			break;
+		case S_IFIFO:
+			if (!terse_flag)
+				puts("Fifo File");
+			mode[0] = 'f';
+			break;
+		default:
+			if (!terse_flag)
+				puts("Unknown");
+			mode[0] = '?';
+		}
+
+		if (terse_flag) {
+			printf("%s %d,%d\n", mode, (int)sbuf.st_uid, (int)sbuf.st_gid);
+			close(fd);
+			continue;
+		}
+
+		printf("  Mode: (%04o/%s)", (unsigned int)(sbuf.st_mode & 07777), mode);
+		printf("         Uid: (%d)", (int)sbuf.st_uid);
+		printf("  Gid: (%d)\n", (int)sbuf.st_gid);
+		printf("Device: %2d,%-2d", major(sbuf.st_dev),
+				minor(sbuf.st_dev));
+		printf("  Inode: %-9llu", (unsigned long long)sbuf.st_ino);
+		printf(" Links: %-5ld", (long)sbuf.st_nlink);
+
+		if ( ((sbuf.st_mode & S_IFMT) == S_IFCHR)
+		    || ((sbuf.st_mode & S_IFMT) == S_IFBLK) )
+			printf("     Device type: %2d,%-2d\n",
+				major(sbuf.st_rdev), minor(sbuf.st_rdev));
+		else
+			printf("\n");
+
+		printf("Access: %.24s",ctime(&sbuf.st_atime));
+		timesince(sbuf.st_atime);
+		printf("Modify: %.24s",ctime(&sbuf.st_mtime));
+		timesince(sbuf.st_mtime);
+		printf("Change: %.24s",ctime(&sbuf.st_ctime));
+		timesince(sbuf.st_ctime);
+
+		if (i+1 < argc)
+			printf("\n");
+
+		close(fd);
+	}
+	exit(0);
+}
diff --git a/tests/cifs/002 b/tests/cifs/002
new file mode 100755
index 00000000..5f1eaffc
--- /dev/null
+++ b/tests/cifs/002
@@ -0,0 +1,62 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026 KylinSoft Co., Ltd. All rights reserved.
+# Author(s): ChenXiaoSong <[email protected]>
+#
+# FS QA Test No. cifs/002
+#
+# Check that fstat(2) returns the correct hard link count for a regular file.
+# Regression test for kernel commit:
+# 9dd1964ac59d ("smb/client: fix incorrect nlink returned by fstat()")
+#
+#  from
+#  tests/generic/002
+#  Copyright (c) 2000-2001 Silicon Graphics, Inc.  All Rights Reserved.
+#
+. ./common/preamble
+_begin_fstest metadata auto quick
+
+# Override the default cleanup function.
+_cleanup()
+{
+	rm -f $tmp.*
+	rm -rf $TEST_DIR/$$
+}
+
+status=0	# success is the default!
+
+_require_test
+_require_hardlinks
+_require_test_program fstat
+
+echo "Silence is goodness ..."
+
+testdir=$TEST_DIR/$$
+mkdir -p $testdir
+
+touch $testdir/tmp.1
+for l in 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+do
+	ln $testdir/tmp.1 $testdir/tmp.$l
+	x=`$here/src/fstat $testdir/tmp.1 | sed -n -e '/ Links: /s/.*Links: *//p'`
+	if [ "$l" -ne $x ]
+	then
+		echo "Arrgh, created link #$l and fstat looks like ..."
+		$here/src/fstat $testdir/tmp.1
+		status=1
+	fi
+done
+
+for l in 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
+do
+	x=`$here/src/fstat $testdir/tmp.1 | sed -n -e '/ Links: /s/.*Links: *//p'`
+	if [ "$l" -ne $x ]
+	then
+		echo "Arrgh, about to remove link #$l and fstat looks like ..."
+		$here/src/fstat $testdir/tmp.1
+		status=1
+	fi
+	rm -f $testdir/tmp.$l
+done
+
+exit
diff --git a/tests/cifs/002.out b/tests/cifs/002.out
new file mode 100644
index 00000000..11426b54
--- /dev/null
+++ b/tests/cifs/002.out
@@ -0,0 +1,2 @@
+QA output created by 002
+Silence is goodness ...
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.