From 67c7e4619188cfd684e59e3cb538948ae8f2b74c Mon Sep 17 00:00:00 2001 From: Patrick Donnelly Date: Wed, 25 Apr 2018 13:07:34 -0700 Subject: [PATCH] client: use common interp of st_nlink for dirs Apparently some applications use this (like mail servers) and since it's trivial to support, let's do it. Idea is that st_nlinks for a directory is either 0 (it is unlinked) or 2 + the number of sub-directories (which have .. parent links). Fixes: https://tracker.ceph.com/issues/23873 Signed-off-by: Patrick Donnelly --- src/client/Client.cc | 34 +++++++++++++++++++-- src/client/Inode.cc | 1 + src/test/libcephfs/test.cc | 62 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/client/Client.cc b/src/client/Client.cc index e9658b13e5f..2a54b8603ea 100644 --- a/src/client/Client.cc +++ b/src/client/Client.cc @@ -7056,7 +7056,22 @@ int Client::fill_stat(Inode *in, struct stat *st, frag_info_t *dirstat, nest_inf st->st_dev = in->snapid; st->st_mode = in->mode; st->st_rdev = in->rdev; - st->st_nlink = in->nlink; + if (in->is_dir()) { + switch (in->nlink) { + case 0: + st->st_nlink = 0; /* dir is unlinked */ + break; + case 1: + st->st_nlink = 1 /* parent dentry */ + + 1 /* /. */ + + in->dirstat.nsubdirs; /* include /. self-reference */ + break; + default: + ceph_abort(); + } + } else { + st->st_nlink = in->nlink; + } st->st_uid = in->uid; st->st_gid = in->gid; if (in->ctime > in->mtime) { @@ -7123,7 +7138,22 @@ void Client::fill_statx(Inode *in, unsigned int mask, struct ceph_statx *stx) } if (mask & CEPH_CAP_LINK_SHARED) { - stx->stx_nlink = in->nlink; + if (in->is_dir()) { + switch (in->nlink) { + case 0: + stx->stx_nlink = 0; /* dir is unlinked */ + break; + case 1: + stx->stx_nlink = 1 /* parent dentry */ + + 1 /* /. */ + + in->dirstat.nsubdirs; /* include /. self-reference */ + break; + default: + ceph_abort(); + } + } else { + stx->stx_nlink = in->nlink; + } stx->stx_mask |= CEPH_STATX_NLINK; } diff --git a/src/client/Inode.cc b/src/client/Inode.cc index 7d9e9ad3ceb..1a03e7d9243 100644 --- a/src/client/Inode.cc +++ b/src/client/Inode.cc @@ -46,6 +46,7 @@ ostream& operator<<(ostream &out, const Inode &in) << " open=" << in.open_by_mode << " mode=" << oct << in.mode << dec << " size=" << in.size << "/" << in.max_size + << " nlink=" << in.nlink << " btime=" << in.btime << " mtime=" << in.mtime << " ctime=" << in.ctime diff --git a/src/test/libcephfs/test.cc b/src/test/libcephfs/test.cc index 1ccd84a0b87..1b79812a5ac 100644 --- a/src/test/libcephfs/test.cc +++ b/src/test/libcephfs/test.cc @@ -614,6 +614,68 @@ TEST(LibCephFS, LstatSlashdot) { ceph_shutdown(cmount); } +TEST(LibCephFS, StatDirNlink) { + struct ceph_mount_info *cmount; + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(ceph_mount(cmount, NULL), 0); + + char test_dir1[256]; + sprintf(test_dir1, "dir1_symlinks_%d", getpid()); + ASSERT_EQ(ceph_mkdir(cmount, test_dir1, 0700), 0); + + int fd = ceph_open(cmount, test_dir1, O_DIRECTORY|O_RDONLY, 0); + ASSERT_GT(fd, 0); + struct ceph_statx stx; + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 2); + + { + char test_dir2[256]; + sprintf(test_dir2, "%s/.", test_dir1); + ASSERT_EQ(ceph_statx(cmount, test_dir2, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 2); + } + + { + char test_dir2[256]; + sprintf(test_dir2, "%s/1", test_dir1); + ASSERT_EQ(ceph_mkdir(cmount, test_dir2, 0700), 0); + ASSERT_EQ(ceph_statx(cmount, test_dir2, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 2); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 3); + sprintf(test_dir2, "%s/2", test_dir1); + ASSERT_EQ(ceph_mkdir(cmount, test_dir2, 0700), 0); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 4); + sprintf(test_dir2, "%s/1/1", test_dir1); + ASSERT_EQ(ceph_mkdir(cmount, test_dir2, 0700), 0); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 4); + ASSERT_EQ(ceph_rmdir(cmount, test_dir2), 0); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 4); + sprintf(test_dir2, "%s/1", test_dir1); + ASSERT_EQ(ceph_rmdir(cmount, test_dir2), 0); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 3); + sprintf(test_dir2, "%s/2", test_dir1); + ASSERT_EQ(ceph_rmdir(cmount, test_dir2), 0); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 2); + } + + ASSERT_EQ(ceph_rmdir(cmount, test_dir1), 0); + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 0); + + ceph_close(cmount, fd); + + ceph_shutdown(cmount); +} + TEST(LibCephFS, DoubleChmod) { struct ceph_mount_info *cmount; -- 2.39.5