diff options
Diffstat (limited to 'src/test/libcephfs')
-rw-r--r-- | src/test/libcephfs/CMakeLists.txt | 96 | ||||
-rw-r--r-- | src/test/libcephfs/access.cc | 399 | ||||
-rw-r--r-- | src/test/libcephfs/acl.cc | 367 | ||||
-rw-r--r-- | src/test/libcephfs/caps.cc | 95 | ||||
-rw-r--r-- | src/test/libcephfs/ceph_pthread_self.h | 31 | ||||
-rw-r--r-- | src/test/libcephfs/deleg.cc | 401 | ||||
-rw-r--r-- | src/test/libcephfs/flock.cc | 654 | ||||
-rw-r--r-- | src/test/libcephfs/lazyio.cc | 356 | ||||
-rw-r--r-- | src/test/libcephfs/main.cc | 50 | ||||
-rw-r--r-- | src/test/libcephfs/monconfig.cc | 101 | ||||
-rw-r--r-- | src/test/libcephfs/multiclient.cc | 180 | ||||
-rw-r--r-- | src/test/libcephfs/newops.cc | 87 | ||||
-rw-r--r-- | src/test/libcephfs/quota.cc | 167 | ||||
-rw-r--r-- | src/test/libcephfs/readdir_r_cb.cc | 65 | ||||
-rw-r--r-- | src/test/libcephfs/reclaim.cc | 163 | ||||
-rw-r--r-- | src/test/libcephfs/recordlock.cc | 1105 | ||||
-rw-r--r-- | src/test/libcephfs/snapdiff.cc | 1684 | ||||
-rw-r--r-- | src/test/libcephfs/suidsgid.cc | 331 | ||||
-rw-r--r-- | src/test/libcephfs/test.cc | 3775 | ||||
-rw-r--r-- | src/test/libcephfs/vxattr.cc | 385 |
20 files changed, 10492 insertions, 0 deletions
diff --git a/src/test/libcephfs/CMakeLists.txt b/src/test/libcephfs/CMakeLists.txt new file mode 100644 index 000000000..672e6dd8f --- /dev/null +++ b/src/test/libcephfs/CMakeLists.txt @@ -0,0 +1,96 @@ +if(WITH_LIBCEPHFS) + add_executable(ceph_test_libcephfs + test.cc + readdir_r_cb.cc + caps.cc + multiclient.cc + flock.cc + recordlock.cc + acl.cc + main.cc + deleg.cc + monconfig.cc + vxattr.cc + snapdiff.cc + ) + target_link_libraries(ceph_test_libcephfs + ceph-common + cephfs + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + install(TARGETS ceph_test_libcephfs + DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(ceph_test_libcephfs_suidsgid + suidsgid.cc + ) + target_link_libraries(ceph_test_libcephfs_suidsgid + ceph-common + cephfs + librados + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + install(TARGETS ceph_test_libcephfs_suidsgid + DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(ceph_test_libcephfs_newops + main.cc + newops.cc + ) + target_link_libraries(ceph_test_libcephfs_newops + ceph-common + cephfs + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + install(TARGETS ceph_test_libcephfs_newops + DESTINATION ${CMAKE_INSTALL_BINDIR}) + + # uses fork, not available on Windows + if(NOT WIN32) + add_executable(ceph_test_libcephfs_reclaim + reclaim.cc + ) + target_link_libraries(ceph_test_libcephfs_reclaim + cephfs + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + install(TARGETS ceph_test_libcephfs_reclaim + DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif(NOT WIN32) + + add_executable(ceph_test_libcephfs_lazyio + lazyio.cc + ) + target_link_libraries(ceph_test_libcephfs_lazyio + cephfs + librados + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + install(TARGETS ceph_test_libcephfs_lazyio + DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(ceph_test_libcephfs_access + test.cc + access.cc + ) + target_link_libraries(ceph_test_libcephfs_access + ceph-common + cephfs + librados + ${UNITTEST_LIBS} + ${EXTRALIBS} + ${CMAKE_DL_LIBS} + ) + install(TARGETS ceph_test_libcephfs_access + DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif(WITH_LIBCEPHFS) diff --git a/src/test/libcephfs/access.cc b/src/test/libcephfs/access.cc new file mode 100644 index 000000000..57b1a89fa --- /dev/null +++ b/src/test/libcephfs/access.cc @@ -0,0 +1,399 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "common/ceph_argparse.h" +#include "include/buffer.h" +#include "include/stringify.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "include/rados/librados.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> +#include <iostream> +#include <vector> +#include "json_spirit/json_spirit.h" + +#include "include/fs_types.h" + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +using namespace std; + +rados_t cluster; + +string key; + +int do_mon_command(string s, string *key) +{ + char *outs, *outbuf; + size_t outs_len, outbuf_len; + const char *ss = s.c_str(); + int r = rados_mon_command(cluster, (const char **)&ss, 1, + 0, 0, + &outbuf, &outbuf_len, + &outs, &outs_len); + if (outbuf_len) { + string s(outbuf, outbuf_len); + std::cout << "out: " << s << std::endl; + + // parse out the key + json_spirit::mValue v, k; + json_spirit::read_or_throw(s, v); + k = v.get_array()[0].get_obj().find("key")->second; + *key = k.get_str(); + std::cout << "key: " << *key << std::endl; + free(outbuf); + } else { + return -CEPHFS_EINVAL; + } + if (outs_len) { + string s(outs, outs_len); + std::cout << "outs: " << s << std::endl; + free(outs); + } + return r; +} + +string get_unique_dir() +{ + return string("/ceph_test_libcephfs_access.") + stringify(rand()); +} + +TEST(AccessTest, Foo) { + string dir = get_unique_dir(); + string user = "libcephfs_foo_test." + stringify(rand()); + // admin mount to set up test + struct ceph_mount_info *admin; + ASSERT_EQ(0, ceph_create(&admin, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(admin, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(admin, NULL)); + ASSERT_EQ(0, ceph_mount(admin, "/")); + ASSERT_EQ(0, ceph_mkdir(admin, dir.c_str(), 0755)); + + // create access key + string key; + ASSERT_EQ(0, do_mon_command( + "{\"prefix\": \"auth get-or-create\", \"entity\": \"client." + user + "\", " + "\"caps\": [\"mon\", \"allow *\", \"osd\", \"allow rw\", " + "\"mds\", \"allow rw\"" + "], \"format\": \"json\"}", &key)); + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, user.c_str())); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_set(cmount, "key", key.c_str())); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ceph_shutdown(cmount); + + // clean up + ASSERT_EQ(0, ceph_rmdir(admin, dir.c_str())); + ceph_shutdown(admin); +} + +TEST(AccessTest, Path) { + string good = get_unique_dir(); + string bad = get_unique_dir(); + string user = "libcephfs_path_test." + stringify(rand()); + struct ceph_mount_info *admin; + ASSERT_EQ(0, ceph_create(&admin, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(admin, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(admin, NULL)); + ASSERT_EQ(0, ceph_mount(admin, "/")); + ASSERT_EQ(0, ceph_mkdir(admin, good.c_str(), 0755)); + ASSERT_EQ(0, ceph_mkdir(admin, string(good + "/p").c_str(), 0755)); + ASSERT_EQ(0, ceph_mkdir(admin, bad.c_str(), 0755)); + ASSERT_EQ(0, ceph_mkdir(admin, string(bad + "/p").c_str(), 0755)); + int fd = ceph_open(admin, string(good + "/q").c_str(), O_CREAT|O_WRONLY, 0755); + ceph_close(admin, fd); + fd = ceph_open(admin, string(bad + "/q").c_str(), O_CREAT|O_WRONLY, 0755); + ceph_close(admin, fd); + fd = ceph_open(admin, string(bad + "/z").c_str(), O_CREAT|O_WRONLY, 0755); + ceph_write(admin, fd, "TEST FAILED", 11, 0); + ceph_close(admin, fd); + + string key; + ASSERT_EQ(0, do_mon_command( + "{\"prefix\": \"auth get-or-create\", \"entity\": \"client." + user + "\", " + "\"caps\": [\"mon\", \"allow r\", \"osd\", \"allow rwx\", " + "\"mds\", \"allow r, allow rw path=" + good + "\"" + "], \"format\": \"json\"}", &key)); + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, user.c_str())); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_set(cmount, "key", key.c_str())); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + // allowed + ASSERT_GE(ceph_mkdir(cmount, string(good + "/x").c_str(), 0755), 0); + ASSERT_GE(ceph_rmdir(cmount, string(good + "/p").c_str()), 0); + ASSERT_GE(ceph_unlink(cmount, string(good + "/q").c_str()), 0); + fd = ceph_open(cmount, string(good + "/y").c_str(), O_CREAT|O_WRONLY, 0755); + ASSERT_GE(fd, 0); + ceph_write(cmount, fd, "bar", 3, 0); + ceph_close(cmount, fd); + ASSERT_GE(ceph_unlink(cmount, string(good + "/y").c_str()), 0); + ASSERT_GE(ceph_rmdir(cmount, string(good + "/x").c_str()), 0); + + fd = ceph_open(cmount, string(bad + "/z").c_str(), O_RDONLY, 0644); + ASSERT_GE(fd, 0); + ceph_close(cmount, fd); + + // not allowed + ASSERT_LT(ceph_mkdir(cmount, string(bad + "/x").c_str(), 0755), 0); + ASSERT_LT(ceph_rmdir(cmount, string(bad + "/p").c_str()), 0); + ASSERT_LT(ceph_unlink(cmount, string(bad + "/q").c_str()), 0); + fd = ceph_open(cmount, string(bad + "/y").c_str(), O_CREAT|O_WRONLY, 0755); + ASSERT_LT(fd, 0); + + // unlink open file + fd = ceph_open(cmount, string(good + "/unlinkme").c_str(), O_CREAT|O_WRONLY, 0755); + ceph_unlink(cmount, string(good + "/unlinkme").c_str()); + ASSERT_GE(ceph_write(cmount, fd, "foo", 3, 0), 0); + ASSERT_GE(ceph_fchmod(cmount, fd, 0777), 0); + ASSERT_GE(ceph_ftruncate(cmount, fd, 0), 0); + ASSERT_GE(ceph_fsetxattr(cmount, fd, "user.any", "bar", 3, 0), 0); + ceph_close(cmount, fd); + + // rename open file + fd = ceph_open(cmount, string(good + "/renameme").c_str(), O_CREAT|O_WRONLY, 0755); + ASSERT_EQ(ceph_rename(admin, string(good + "/renameme").c_str(), + string(bad + "/asdf").c_str()), 0); + ASSERT_GE(ceph_write(cmount, fd, "foo", 3, 0), 0); + ASSERT_GE(ceph_fchmod(cmount, fd, 0777), -CEPHFS_EACCES); + ASSERT_GE(ceph_ftruncate(cmount, fd, 0), -CEPHFS_EACCES); + ASSERT_GE(ceph_fsetxattr(cmount, fd, "user.any", "bar", 3, 0), -CEPHFS_EACCES); + ceph_close(cmount, fd); + + ceph_shutdown(cmount); + ASSERT_EQ(0, ceph_unlink(admin, string(bad + "/q").c_str())); + ASSERT_EQ(0, ceph_unlink(admin, string(bad + "/z").c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, string(bad + "/p").c_str())); + ASSERT_EQ(0, ceph_unlink(admin, string(bad + "/asdf").c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, good.c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, bad.c_str())); + ceph_shutdown(admin); +} + +TEST(AccessTest, ReadOnly) { + string dir = get_unique_dir(); + string dir2 = get_unique_dir(); + string user = "libcephfs_readonly_test." + stringify(rand()); + struct ceph_mount_info *admin; + ASSERT_EQ(0, ceph_create(&admin, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(admin, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(admin, NULL)); + ASSERT_EQ(0, ceph_mount(admin, "/")); + ASSERT_EQ(0, ceph_mkdir(admin, dir.c_str(), 0755)); + int fd = ceph_open(admin, string(dir + "/out").c_str(), O_CREAT|O_WRONLY, 0755); + ceph_write(admin, fd, "foo", 3, 0); + ceph_close(admin,fd); + + string key; + ASSERT_EQ(0, do_mon_command( + "{\"prefix\": \"auth get-or-create\", \"entity\": \"client." + user + "\", " + "\"caps\": [\"mon\", \"allow r\", \"osd\", \"allow rw\", " + "\"mds\", \"allow r\"" + "], \"format\": \"json\"}", &key)); + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, user.c_str())); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_set(cmount, "key", key.c_str())); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + // allowed + fd = ceph_open(cmount, string(dir + "/out").c_str(), O_RDONLY, 0644); + ASSERT_GE(fd, 0); + ceph_close(cmount,fd); + + // not allowed + fd = ceph_open(cmount, string(dir + "/bar").c_str(), O_CREAT|O_WRONLY, 0755); + ASSERT_LT(fd, 0); + ASSERT_LT(ceph_mkdir(cmount, dir2.c_str(), 0755), 0); + + ceph_shutdown(cmount); + ASSERT_EQ(0, ceph_unlink(admin, string(dir + "/out").c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, dir.c_str())); + ceph_shutdown(admin); +} + +TEST(AccessTest, User) { + string dir = get_unique_dir(); + string user = "libcephfs_user_test." + stringify(rand()); + + // admin mount to set up test + struct ceph_mount_info *admin; + ASSERT_EQ(0, ceph_create(&admin, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(admin, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(admin, NULL)); + ASSERT_EQ(0, ceph_conf_set(admin, "client_permissions", "0")); + ASSERT_EQ(0, ceph_mount(admin, "/")); + ASSERT_EQ(0, ceph_mkdir(admin, dir.c_str(), 0755)); + + // create access key + string key; + ASSERT_EQ(0, do_mon_command( + "{\"prefix\": \"auth get-or-create\", \"entity\": \"client." + user + "\", " + "\"caps\": [\"mon\", \"allow *\", \"osd\", \"allow rw\", " + "\"mds\", \"allow rw uid=123 gids=456,789\"" + "], \"format\": \"json\"}", &key)); + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, user.c_str())); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_set(cmount, "key", key.c_str())); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_init(cmount)); + + UserPerm *perms = ceph_userperm_new(123, 456, 0, NULL); + ASSERT_NE(nullptr, perms); + ASSERT_EQ(0, ceph_mount_perms_set(cmount, perms)); + ceph_userperm_destroy(perms); + + ASSERT_EQ(0, ceph_conf_set(cmount, "client_permissions", "0")); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + // user bits + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 0700)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 123, 456)); + ASSERT_EQ(0, ceph_mkdir(cmount, string(dir + "/u1").c_str(), 0755)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + + // group bits + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 0770)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 456)); + ASSERT_EQ(0, ceph_mkdir(cmount, string(dir + "/u2").c_str(), 0755)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 2)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + + // user overrides group + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 0470)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 123, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + + // other + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 0777)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 1)); + ASSERT_EQ(0, ceph_mkdir(cmount, string(dir + "/u3").c_str(), 0755)); + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 0770)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + + // user and group overrides other + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 07)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 123, 1)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 123, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_mkdir(cmount, string(dir + "/no").c_str(), 0755)); + + // chown and chgrp + ASSERT_EQ(0, ceph_chmod(admin, dir.c_str(), 0700)); + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 123, 456)); + // FIXME: Re-enable these 789 tests once we can set multiple GIDs via libcephfs/config + // ASSERT_EQ(0, ceph_chown(cmount, dir.c_str(), 123, 789)); + ASSERT_EQ(0, ceph_chown(cmount, dir.c_str(), 123, 456)); + // ASSERT_EQ(0, ceph_chown(cmount, dir.c_str(), -1, 789)); + ASSERT_EQ(0, ceph_chown(cmount, dir.c_str(), -1, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), 123, 1)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), 1, 456)); + + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 1)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), 123, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), 123, -1)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), -1, 456)); + + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 1, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), 123, 456)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), 123, -1)); + ASSERT_EQ(-CEPHFS_EACCES, ceph_chown(cmount, dir.c_str(), -1, 456)); + + ASSERT_EQ(0, ceph_chown(admin, dir.c_str(), 123, 1)); + ASSERT_EQ(0, ceph_chown(cmount, dir.c_str(), -1, 456)); + // ASSERT_EQ(0, ceph_chown(cmount, dir.c_str(), 123, 789)); + + ceph_shutdown(cmount); + + // clean up + ASSERT_EQ(0, ceph_rmdir(admin, string(dir + "/u1").c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, string(dir + "/u2").c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, string(dir + "/u3").c_str())); + ASSERT_EQ(0, ceph_rmdir(admin, dir.c_str())); + ceph_shutdown(admin); +} + +static int update_root_mode() +{ + struct ceph_mount_info *admin; + int r = ceph_create(&admin, NULL); + if (r < 0) + return r; + ceph_conf_read_file(admin, NULL); + ceph_conf_parse_env(admin, NULL); + ceph_conf_set(admin, "client_permissions", "false"); + r = ceph_mount(admin, "/"); + if (r < 0) + goto out; + r = ceph_chmod(admin, "/", 0777); +out: + ceph_shutdown(admin); + return r; +} + + +int main(int argc, char **argv) +{ + int r = update_root_mode(); + if (r < 0) + exit(1); + + ::testing::InitGoogleTest(&argc, argv); + + srand(getpid()); + + r = rados_create(&cluster, NULL); + if (r < 0) + exit(1); + + r = rados_conf_read_file(cluster, NULL); + if (r < 0) + exit(1); + + rados_conf_parse_env(cluster, NULL); + r = rados_connect(cluster); + if (r < 0) + exit(1); + + r = RUN_ALL_TESTS(); + + rados_shutdown(cluster); + + return r; +} diff --git a/src/test/libcephfs/acl.cc b/src/test/libcephfs/acl.cc new file mode 100644 index 000000000..e263ef2fb --- /dev/null +++ b/src/test/libcephfs/acl.cc @@ -0,0 +1,367 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "include/types.h" +#include "gtest/gtest.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "include/ceph_fs.h" +#include "client/posix_acl.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef __linux__ +#include <sys/xattr.h> +#endif + +static size_t acl_ea_size(int count) +{ + return sizeof(acl_ea_header) + count * sizeof(acl_ea_entry); +} + +static int acl_ea_count(size_t size) +{ + if (size < sizeof(acl_ea_header)) + return -1; + size -= sizeof(acl_ea_header); + if (size % sizeof(acl_ea_entry)) + return -1; + return size / sizeof(acl_ea_entry); +} + +static int check_acl_and_mode(const void *buf, size_t size, mode_t mode) +{ + const acl_ea_entry *group_entry = NULL, *mask_entry = NULL; + const acl_ea_header *header = reinterpret_cast<const acl_ea_header*>(buf); + const acl_ea_entry *entry = header->a_entries; + int count = (size - sizeof(*header)) / sizeof(*entry); + for (int i = 0; i < count; ++i) { + __u16 tag = entry->e_tag; + __u16 perm = entry->e_perm; + switch(tag) { + case ACL_USER_OBJ: + if (perm != ((mode >> 6) & 7)) + return -CEPHFS_EINVAL; + break; + case ACL_USER: + case ACL_GROUP: + break; + case ACL_GROUP_OBJ: + group_entry = entry; + break; + case ACL_OTHER: + if (perm != (mode & 7)) + return -CEPHFS_EINVAL; + break; + case ACL_MASK: + mask_entry = entry; + break; + default: + return -CEPHFS_EIO; + } + ++entry; + } + if (mask_entry) { + __u16 perm = mask_entry->e_perm; + if (perm != ((mode >> 3) & 7)) + return -CEPHFS_EINVAL; + } else { + if (!group_entry) + return -CEPHFS_EIO; + __u16 perm = group_entry->e_perm; + if (perm != ((mode >> 3) & 7)) + return -CEPHFS_EINVAL; + } + return 0; +} + +static int generate_test_acl(void *buf, size_t size, mode_t mode) +{ + if (acl_ea_count(size) != 5) + return -1; + acl_ea_header *header = reinterpret_cast<acl_ea_header*>(buf); + header->a_version = (__u32)ACL_EA_VERSION; + acl_ea_entry *entry = header->a_entries; + entry->e_tag = ACL_USER_OBJ; + entry->e_perm = (mode >> 6) & 7; + ++entry; + entry->e_tag = ACL_USER; + entry->e_perm = 7; + entry->e_id = getuid(); + ++entry; + entry->e_tag = ACL_GROUP_OBJ; + entry->e_perm = (mode >> 3) & 7; + ++entry; + entry->e_tag = ACL_MASK; + entry->e_perm = 7; + ++entry; + entry->e_tag = ACL_OTHER; + entry->e_perm = mode & 7; + return 0; +} + +static int generate_empty_acl(void *buf, size_t size, mode_t mode) +{ + + if (acl_ea_count(size) != 3) + return -1; + acl_ea_header *header = reinterpret_cast<acl_ea_header*>(buf); + header->a_version = (__u32)ACL_EA_VERSION; + acl_ea_entry *entry = header->a_entries; + entry->e_tag = ACL_USER_OBJ; + entry->e_perm = (mode >> 6) & 7; + ++entry; + entry->e_tag = ACL_GROUP_OBJ; + entry->e_perm = (mode >> 3) & 7; + ++entry; + entry->e_tag = ACL_OTHER; + entry->e_perm = mode & 7; + return 0; +} + +TEST(ACL, SetACL) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_conf_set(cmount, "client_acl_type", "posix_acl")); + ASSERT_EQ(0, ceph_conf_set(cmount, "client_permissions", "0")); + + char test_file[256]; + sprintf(test_file, "file1_setacl_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0600); + ASSERT_GT(fd, 0); + // change ownership to nobody -- we assume nobody exists and id is always 65534 + ASSERT_EQ(ceph_fchown(cmount, fd, 65534, 65534), 0); + + ASSERT_EQ(0, ceph_conf_set(cmount, "client_permissions", "1")); + // "nobody" will be ignored on Windows + #ifndef _WIN32 + ASSERT_EQ(ceph_open(cmount, test_file, O_RDWR, 0), -CEPHFS_EACCES); + #endif + ASSERT_EQ(0, ceph_conf_set(cmount, "client_permissions", "0")); + + size_t acl_buf_size = acl_ea_size(5); + void *acl_buf = malloc(acl_buf_size); + ASSERT_EQ(generate_test_acl(acl_buf, acl_buf_size, 0750), 0); + + // can't set default acl for non-directory + ASSERT_EQ(ceph_fsetxattr(cmount, fd, ACL_EA_DEFAULT, acl_buf, acl_buf_size, 0), -CEPHFS_EACCES); + ASSERT_EQ(ceph_fsetxattr(cmount, fd, ACL_EA_ACCESS, acl_buf, acl_buf_size, 0), 0); + + int tmpfd = ceph_open(cmount, test_file, O_RDWR, 0); + ASSERT_GT(tmpfd, 0); + ceph_close(cmount, tmpfd); + + struct ceph_statx stx; + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_MODE, 0), 0); + // mode was modified according to ACL + ASSERT_EQ(stx.stx_mode & 0777u, 0770u); + ASSERT_EQ(check_acl_and_mode(acl_buf, acl_buf_size, stx.stx_mode), 0); + + acl_buf_size = acl_ea_size(3); + // setting ACL that is equivalent to file mode + ASSERT_EQ(generate_empty_acl(acl_buf, acl_buf_size, 0600), 0); + ASSERT_EQ(ceph_fsetxattr(cmount, fd, ACL_EA_ACCESS, acl_buf, acl_buf_size, 0), 0); + // ACL was deleted + ASSERT_EQ(ceph_fgetxattr(cmount, fd, ACL_EA_ACCESS, NULL, 0), -CEPHFS_ENODATA); + + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_MODE, 0), 0); + // mode was modified according to ACL + ASSERT_EQ(stx.stx_mode & 0777u, 0600u); + + free(acl_buf); + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(ACL, Chmod) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_conf_set(cmount, "client_acl_type", "posix_acl")); + + char test_file[256]; + sprintf(test_file, "file1_acl_chmod_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0600); + ASSERT_GT(fd, 0); + + int acl_buf_size = acl_ea_size(5); + void *acl_buf = malloc(acl_buf_size); + ASSERT_EQ(generate_test_acl(acl_buf, acl_buf_size, 0775), 0); + ASSERT_EQ(ceph_fsetxattr(cmount, fd, ACL_EA_ACCESS, acl_buf, acl_buf_size, 0), 0); + + struct ceph_statx stx; + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_MODE, 0), 0); + // mode was updated according to ACL + ASSERT_EQ(stx.stx_mode & 0777u, 0775u); + + // change mode + ASSERT_EQ(ceph_fchmod(cmount, fd, 0640), 0); + + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_MODE, 0), 0); + ASSERT_EQ(stx.stx_mode & 0777u, 0640u); + + // ACL was updated according to mode + ASSERT_EQ(ceph_fgetxattr(cmount, fd, ACL_EA_ACCESS, acl_buf, acl_buf_size), acl_buf_size); + ASSERT_EQ(check_acl_and_mode(acl_buf, acl_buf_size, stx.stx_mode), 0); + + free(acl_buf); + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(ACL, DefaultACL) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_conf_set(cmount, "client_acl_type", "posix_acl")); + + int acl_buf_size = acl_ea_size(5); + void *acl1_buf = malloc(acl_buf_size); + void *acl2_buf = malloc(acl_buf_size); + + ASSERT_EQ(generate_test_acl(acl1_buf, acl_buf_size, 0750), 0); + + char test_dir1[256]; + sprintf(test_dir1, "dir1_acl_default_%d", getpid()); + ASSERT_EQ(ceph_mkdir(cmount, test_dir1, 0750), 0); + + // set default acl + ASSERT_EQ(ceph_setxattr(cmount, test_dir1, ACL_EA_DEFAULT, acl1_buf, acl_buf_size, 0), 0); + + char test_dir2[262]; + sprintf(test_dir2, "%s/dir2", test_dir1); + ASSERT_EQ(ceph_mkdir(cmount, test_dir2, 0755), 0); + + // inherit default acl + ASSERT_EQ(ceph_getxattr(cmount, test_dir2, ACL_EA_DEFAULT, acl2_buf, acl_buf_size), acl_buf_size); + ASSERT_EQ(memcmp(acl1_buf, acl2_buf, acl_buf_size), 0); + + // mode and ACL are updated + ASSERT_EQ(ceph_getxattr(cmount, test_dir2, ACL_EA_ACCESS, acl2_buf, acl_buf_size), acl_buf_size); + { + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, test_dir2, &stx, CEPH_STATX_MODE, 0), 0); + // other bits of mode &= acl other perm + ASSERT_EQ(stx.stx_mode & 0777u, 0750u); + ASSERT_EQ(check_acl_and_mode(acl2_buf, acl_buf_size, stx.stx_mode), 0); + } + + char test_file1[262]; + sprintf(test_file1, "%s/file1", test_dir1); + int fd = ceph_open(cmount, test_file1, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + // no default acl + ASSERT_EQ(ceph_fgetxattr(cmount, fd, ACL_EA_DEFAULT, NULL, 0), -CEPHFS_ENODATA); + + // mode and ACL are updated + ASSERT_EQ(ceph_fgetxattr(cmount, fd, ACL_EA_ACCESS, acl2_buf, acl_buf_size), acl_buf_size); + { + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, test_file1, &stx, CEPH_STATX_MODE, 0), 0); + // other bits of mode &= acl other perm + ASSERT_EQ(stx.stx_mode & 0777u, 0660u); + ASSERT_EQ(check_acl_and_mode(acl2_buf, acl_buf_size, stx.stx_mode), 0); + } + + free(acl1_buf); + free(acl2_buf); + ASSERT_EQ(ceph_unlink(cmount, test_file1), 0); + ASSERT_EQ(ceph_rmdir(cmount, test_dir2), 0); + ASSERT_EQ(ceph_rmdir(cmount, test_dir1), 0); + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(ACL, Disabled) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_conf_set(cmount, "client_acl_type", "")); + + size_t acl_buf_size = acl_ea_size(3); + void *acl_buf = malloc(acl_buf_size); + ASSERT_EQ(generate_empty_acl(acl_buf, acl_buf_size, 0755), 0); + + char test_dir[256]; + sprintf(test_dir, "dir1_acl_disabled_%d", getpid()); + ASSERT_EQ(ceph_mkdir(cmount, test_dir, 0750), 0); + + ASSERT_EQ(ceph_setxattr(cmount, test_dir, ACL_EA_DEFAULT, acl_buf, acl_buf_size, 0), -CEPHFS_EOPNOTSUPP); + ASSERT_EQ(ceph_setxattr(cmount, test_dir, ACL_EA_ACCESS, acl_buf, acl_buf_size, 0), -CEPHFS_EOPNOTSUPP); + ASSERT_EQ(ceph_getxattr(cmount, test_dir, ACL_EA_DEFAULT, acl_buf, acl_buf_size), -CEPHFS_EOPNOTSUPP); + ASSERT_EQ(ceph_getxattr(cmount, test_dir, ACL_EA_ACCESS, acl_buf, acl_buf_size), -CEPHFS_EOPNOTSUPP); + + free(acl_buf); + ceph_shutdown(cmount); +} + +TEST(ACL, SnapdirACL) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_conf_set(cmount, "client_acl_type", "posix_acl")); + + int acl_buf_size = acl_ea_size(5); + void *acl1_buf = malloc(acl_buf_size); + void *acl2_buf = malloc(acl_buf_size); + void *acl3_buf = malloc(acl_buf_size); + + ASSERT_EQ(generate_test_acl(acl1_buf, acl_buf_size, 0750), 0); + + char test_dir1[256]; + sprintf(test_dir1, "dir1_acl_default_%d", getpid()); + ASSERT_EQ(ceph_mkdir(cmount, test_dir1, 0750), 0); + + // set default acl + ASSERT_EQ(ceph_setxattr(cmount, test_dir1, ACL_EA_DEFAULT, acl1_buf, acl_buf_size, 0), 0); + + char test_dir2[262]; + sprintf(test_dir2, "%s/dir2", test_dir1); + ASSERT_EQ(ceph_mkdir(cmount, test_dir2, 0755), 0); + + // inherit default acl + ASSERT_EQ(ceph_getxattr(cmount, test_dir2, ACL_EA_DEFAULT, acl2_buf, acl_buf_size), acl_buf_size); + ASSERT_EQ(memcmp(acl1_buf, acl2_buf, acl_buf_size), 0); + + char test_dir2_snapdir[512]; + sprintf(test_dir2_snapdir, "%s/dir2/.snap", test_dir1); + + // inherit default acl + ASSERT_EQ(ceph_getxattr(cmount, test_dir2_snapdir, ACL_EA_DEFAULT, acl3_buf, acl_buf_size), acl_buf_size); + ASSERT_EQ(memcmp(acl2_buf, acl3_buf, acl_buf_size), 0); + + memset(acl2_buf, 0, acl_buf_size); + memset(acl3_buf, 0, acl_buf_size); + + ASSERT_EQ(ceph_getxattr(cmount, test_dir2, ACL_EA_ACCESS, acl2_buf, acl_buf_size), acl_buf_size); + ASSERT_EQ(ceph_getxattr(cmount, test_dir2_snapdir, ACL_EA_ACCESS, acl3_buf, acl_buf_size), acl_buf_size); + ASSERT_EQ(memcmp(acl2_buf, acl3_buf, acl_buf_size), 0); + + free(acl1_buf); + free(acl2_buf); + free(acl3_buf); + ASSERT_EQ(ceph_rmdir(cmount, test_dir2), 0); + ASSERT_EQ(ceph_rmdir(cmount, test_dir1), 0); + ceph_shutdown(cmount); +} diff --git a/src/test/libcephfs/caps.cc b/src/test/libcephfs/caps.cc new file mode 100644 index 000000000..9141f9a6b --- /dev/null +++ b/src/test/libcephfs/caps.cc @@ -0,0 +1,95 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "include/int_types.h" + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/ceph_fs.h" +#include "include/cephfs/libcephfs.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#ifdef __linux__ +#include <sys/xattr.h> +#endif +#include <signal.h> + +TEST(Caps, ReadZero) { + + int mypid = getpid(); + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + int i = 0; + for(; i < 30; ++i) { + + char c_path[1024]; + sprintf(c_path, "/caps_rzfile_%d_%d", mypid, i); + int fd = ceph_open(cmount, c_path, O_CREAT|O_TRUNC|O_WRONLY, 0644); + ASSERT_LT(0, fd); + + int expect = CEPH_CAP_FILE_EXCL | CEPH_CAP_FILE_WR | CEPH_CAP_FILE_BUFFER; + int caps = ceph_debug_get_fd_caps(cmount, fd); + + ASSERT_EQ(expect, caps & expect); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + caps = ceph_debug_get_file_caps(cmount, c_path); + ASSERT_EQ(expect, caps & expect); + + char cw_path[1024]; + sprintf(cw_path, "/caps_wzfile_%d_%d", mypid, i); + int wfd = ceph_open(cmount, cw_path, O_CREAT|O_TRUNC|O_WRONLY, 0644); + ASSERT_LT(0, wfd); + + char wbuf[4096]; + ASSERT_EQ(4096, ceph_write(cmount, wfd, wbuf, 4096, 0)); + + ASSERT_EQ(0, ceph_close(cmount, wfd)); + + struct ceph_statx stx; + ASSERT_EQ(0, ceph_statx(cmount, c_path, &stx, CEPH_STATX_MTIME, 0)); + + caps = ceph_debug_get_file_caps(cmount, c_path); + ASSERT_EQ(expect, caps & expect); + } + + ASSERT_EQ(0, ceph_conf_set(cmount, "client_debug_inject_tick_delay", "20")); + + for(i = 0; i < 30; ++i) { + + char c_path[1024]; + sprintf(c_path, "/caps_rzfile_%d_%d", mypid, i); + + int fd = ceph_open(cmount, c_path, O_RDONLY, 0); + ASSERT_LT(0, fd); + char buf[256]; + + int expect = CEPH_CAP_FILE_RD | CEPH_STAT_CAP_SIZE | CEPH_CAP_FILE_CACHE; + int caps = ceph_debug_get_fd_caps(cmount, fd); + ASSERT_EQ(expect, caps & expect); + ASSERT_EQ(0, ceph_read(cmount, fd, buf, 256, 0)); + + caps = ceph_debug_get_fd_caps(cmount, fd); + ASSERT_EQ(expect, caps & expect); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + } + ceph_shutdown(cmount); +} diff --git a/src/test/libcephfs/ceph_pthread_self.h b/src/test/libcephfs/ceph_pthread_self.h new file mode 100644 index 000000000..9e3cdfa99 --- /dev/null +++ b/src/test/libcephfs/ceph_pthread_self.h @@ -0,0 +1,31 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBCEPHFS_PTHREAD_SELF +#define CEPH_TEST_LIBCEPHFS_PTHREAD_SELF + +#include <pthread.h> + +#include <type_traits> + +/* + * There is a difference between libc shipped with FreeBSD and + * glibc shipped with GNU/Linux for the return type of pthread_self(). + * + * Introduced a conversion function in include/compat.h + * (uint64_t)ceph_pthread_self() + * + * libc returns an opague pthread_t that is not default convertable + * to a uint64_t, which is what gtest expects. + * And tests using gtest will not compile because of this difference. + * + */ +static uint64_t ceph_pthread_self() { + auto me = pthread_self(); + static_assert(std::is_convertible_v<decltype(me), uint64_t> || + std::is_pointer_v<decltype(me)>, + "we need to use pthread_self() for the owner parameter"); + return static_cast<uint64_t>(me); +} + +#endif diff --git a/src/test/libcephfs/deleg.cc b/src/test/libcephfs/deleg.cc new file mode 100644 index 000000000..061e13763 --- /dev/null +++ b/src/test/libcephfs/deleg.cc @@ -0,0 +1,401 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Tests for Ceph delegation handling + * + * (c) 2017, Jeff Layton <jlayton@redhat.com> + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +#include <map> +#include <vector> +#include <thread> +#include <atomic> + +#include "include/ceph_assert.h" + +/* in ms -- 1 minute */ +#define MAX_WAIT (60 * 1000) + +static void wait_for_atomic_bool(std::atomic_bool &recalled) +{ + int i = 0; + + while (!recalled.load()) { + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } +} + +static int ceph_ll_delegation_wait(struct ceph_mount_info *cmount, Fh *fh, + unsigned cmd, ceph_deleg_cb_t cb, void *priv) +{ + int ret, retry = 0; + + /* Wait 10s at most */ + do { + ret = ceph_ll_delegation(cmount, fh, cmd, cb, priv); + usleep(10000); + } while (ret == -CEPHFS_EAGAIN && retry++ < 1000); + + return ret; +} + +static int set_default_deleg_timeout(struct ceph_mount_info *cmount) +{ + uint32_t session_timeout = ceph_get_cap_return_timeout(cmount); + return ceph_set_deleg_timeout(cmount, session_timeout - 1); +} + +static void dummy_deleg_cb(Fh *fh, void *priv) +{ + std::atomic_bool *recalled = (std::atomic_bool *)priv; + recalled->store(true); +} + +static void open_breaker_func(struct ceph_mount_info *cmount, const char *filename, int flags, std::atomic_bool *opened) +{ + bool do_shutdown = false; + + if (!cmount) { + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); + ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0); + ASSERT_EQ(ceph_mount(cmount, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + do_shutdown = true; + } + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_lookup(cmount, root, filename, &file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + int ret, i = 0; + for (;;) { + ASSERT_EQ(ceph_ll_getattr(cmount, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + ret = ceph_ll_open(cmount, file, flags, &fh, perms); + if (ret != -CEPHFS_EAGAIN) + break; + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } + ASSERT_EQ(ret, 0); + opened->store(true); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + + if (do_shutdown) + ceph_shutdown(cmount); +} + +enum { + DelegTestLink, + DelegTestRename, + DelegTestUnlink +}; + +static void namespace_breaker_func(struct ceph_mount_info *cmount, int cmd, const char *oldname, const char *newname) +{ + bool do_shutdown = false; + + if (!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, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + do_shutdown = true; + } + + Inode *root, *file = nullptr; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + int ret, i = 0; + for (;;) { + switch (cmd) { + case DelegTestRename: + ret = ceph_ll_rename(cmount, root, oldname, root, newname, perms); + break; + case DelegTestLink: + if (!file) { + ASSERT_EQ(ceph_ll_lookup(cmount, root, oldname, &file, &stx, 0, 0, perms), 0); + } + ret = ceph_ll_link(cmount, file, root, newname, perms); + break; + case DelegTestUnlink: + ret = ceph_ll_unlink(cmount, root, oldname, perms); + break; + default: + // Bad command + ceph_abort(); + } + if (ret != -CEPHFS_EAGAIN) + break; + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } + ASSERT_EQ(ret, 0); + + if (do_shutdown) + ceph_shutdown(cmount); +} + +static void simple_deleg_test(struct ceph_mount_info *cmount, struct ceph_mount_info *tcmount) +{ + Inode *root, *file; + + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + char filename[32]; + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + std::atomic_bool recalled(false); + std::atomic_bool opened(false); + + // ensure r/w open breaks a r/w delegation + sprintf(filename, "deleg.rwrw.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker1(open_breaker_func, tcmount, filename, O_RDWR, &opened); + + wait_for_atomic_bool(recalled); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker1.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + + // ensure r/o open breaks a r/w delegation + recalled.store(false); + opened.store(false); + sprintf(filename, "deleg.rorw.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker2(open_breaker_func, tcmount, filename, O_RDONLY, &opened); + wait_for_atomic_bool(recalled); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker2.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + + // ensure r/o open does not break a r/o delegation + sprintf(filename, "deleg.rwro.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDONLY|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + recalled.store(false); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); + std::thread breaker3(open_breaker_func, tcmount, filename, O_RDONLY, &opened); + breaker3.join(); + ASSERT_EQ(recalled.load(), false); + + // ensure that r/w open breaks r/o delegation + opened.store(false); + std::thread breaker4(open_breaker_func, tcmount, filename, O_WRONLY, &opened); + wait_for_atomic_bool(recalled); + usleep(1000); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker4.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + + // ensure hardlinking breaks a r/w delegation + recalled.store(false); + char newname[32]; + sprintf(filename, "deleg.old.%x", getpid()); + sprintf(newname, "deleg.new.%x", getpid()); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker5(namespace_breaker_func, tcmount, DelegTestLink, filename, newname); + wait_for_atomic_bool(recalled); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker5.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, newname, perms), 0); + + // ensure renaming breaks a r/w delegation + recalled.store(false); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker6(namespace_breaker_func, tcmount, DelegTestRename, filename, newname); + wait_for_atomic_bool(recalled); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker6.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_unlink(cmount, root, newname, perms), 0); + + // ensure unlinking breaks a r/w delegation + recalled.store(false); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); + std::thread breaker7(namespace_breaker_func, tcmount, DelegTestUnlink, filename, nullptr); + wait_for_atomic_bool(recalled); + ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); + breaker7.join(); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); +} + +TEST(LibCephFS, DelegMultiClient) { + 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, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + + simple_deleg_test(cmount, nullptr); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DelegSingleClient) { + 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, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount), 0); + + simple_deleg_test(cmount, cmount); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DelegTimeout) { + 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, "/"), 0); + // tweak timeout to run quickly, since we don't plan to return it anyway + ASSERT_EQ(ceph_set_deleg_timeout(cmount, 2), 0); + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + char filename[32]; + sprintf(filename, "delegtimeo%x", getpid()); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + + /* Reopen read-only */ + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(ceph_ll_open(cmount, file, O_RDONLY, &fh, perms), 0); + + std::atomic_bool recalled(false); + ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); + std::atomic_bool opened(false); + std::thread breaker1(open_breaker_func, nullptr, filename, O_RDWR, &opened); + breaker1.join(); + ASSERT_EQ(recalled.load(), true); + ASSERT_EQ(ceph_ll_getattr(cmount, root, &stx, 0, 0, perms), -CEPHFS_ENOTCONN); + ceph_release(cmount); +} + +TEST(LibCephFS, RecalledGetattr) { + struct ceph_mount_info *cmount1; + ASSERT_EQ(ceph_create(&cmount1, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount1, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount1, NULL)); + ASSERT_EQ(ceph_mount(cmount1, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount1), 0); + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount1, &root), 0); + + char filename[32]; + sprintf(filename, "recalledgetattr%x", getpid()); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount1); + + ASSERT_EQ(ceph_ll_create(cmount1, root, filename, 0666, + O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_write(cmount1, fh, 0, sizeof(filename), filename), + static_cast<int>(sizeof(filename))); + ASSERT_EQ(ceph_ll_close(cmount1, fh), 0); + + /* New mount for read delegation */ + struct ceph_mount_info *cmount2; + ASSERT_EQ(ceph_create(&cmount2, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount2, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount2, NULL)); + ASSERT_EQ(ceph_mount(cmount2, "/"), 0); + ASSERT_EQ(set_default_deleg_timeout(cmount2), 0); + + ASSERT_EQ(ceph_ll_lookup_root(cmount2, &root), 0); + perms = ceph_mount_perms(cmount2); + ASSERT_EQ(ceph_ll_lookup(cmount2, root, filename, &file, &stx, 0, 0, perms), 0); + + ASSERT_EQ(ceph_ll_open(cmount2, file, O_WRONLY, &fh, perms), 0); + ASSERT_EQ(ceph_ll_write(cmount2, fh, 0, sizeof(filename), filename), + static_cast<int>(sizeof(filename))); + ASSERT_EQ(ceph_ll_close(cmount2, fh), 0); + + ASSERT_EQ(ceph_ll_open(cmount2, file, O_RDONLY, &fh, perms), 0); + + /* Break delegation */ + std::atomic_bool recalled(false); + ASSERT_EQ(ceph_ll_delegation_wait(cmount2, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); + ASSERT_EQ(ceph_ll_read(cmount2, fh, 0, sizeof(filename), filename), + static_cast<int>(sizeof(filename))); + ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + std::atomic_bool opened(false); + std::thread breaker1(open_breaker_func, cmount1, filename, O_WRONLY, &opened); + int i = 0; + do { + ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + ASSERT_LT(i++, MAX_WAIT); + usleep(1000); + } while (!recalled.load()); + ASSERT_EQ(opened.load(), false); + ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); + ASSERT_EQ(ceph_ll_delegation(cmount2, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, nullptr), 0); + breaker1.join(); + ASSERT_EQ(ceph_ll_close(cmount2, fh), 0); + ceph_unmount(cmount2); + ceph_release(cmount2); + ceph_unmount(cmount1); + ceph_release(cmount1); +} diff --git a/src/test/libcephfs/flock.cc b/src/test/libcephfs/flock.cc new file mode 100644 index 000000000..367483d07 --- /dev/null +++ b/src/test/libcephfs/flock.cc @@ -0,0 +1,654 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <pthread.h> +#include "gtest/gtest.h" +#ifndef GTEST_IS_THREADSAFE +#error "!GTEST_IS_THREADSAFE" +#endif + +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdlib.h> +#include <semaphore.h> +#include <time.h> + +#ifndef _WIN32 +#include <sys/mman.h> +#endif + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#elif __FreeBSD__ +#include <sys/types.h> +#include <sys/wait.h> +#endif + +#include "include/ceph_assert.h" +#include "ceph_pthread_self.h" + +// Startup common: create and mount ceph fs +#define STARTUP_CEPH() do { \ + ASSERT_EQ(0, ceph_create(&cmount, NULL)); \ + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); \ + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); \ + ASSERT_EQ(0, ceph_mount(cmount, NULL)); \ + } while(0) + +// Cleanup common: unmount and release ceph fs +#define CLEANUP_CEPH() do { \ + ASSERT_EQ(0, ceph_unmount(cmount)); \ + ASSERT_EQ(0, ceph_release(cmount)); \ + } while(0) + +static const mode_t fileMode = S_IRWXU | S_IRWXG | S_IRWXO; + +// Default wait time for normal and "slow" operations +// (5" should be enough in case of network congestion) +static const long waitMs = 10; +static const long waitSlowMs = 5000; + +// Get the absolute struct timespec reference from now + 'ms' milliseconds +static const struct timespec* abstime(struct timespec &ts, long ms) { + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ceph_abort(); + } + ts.tv_nsec += ms * 1000000; + ts.tv_sec += ts.tv_nsec / 1000000000; + ts.tv_nsec %= 1000000000; + return &ts; +} + +/* Basic locking */ +TEST(LibCephFS, BasicLocking) { + struct ceph_mount_info *cmount = NULL; + STARTUP_CEPH(); + + char c_file[1024]; + sprintf(c_file, "/flock_test_%d", getpid()); + const int fd = ceph_open(cmount, c_file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + + // Lock exclusively twice + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, 42)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 43)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 44)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 42)); + + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 43)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 44)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 43)); + + // Lock shared three times + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, 42)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, 43)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, 44)); + // And then attempt to lock exclusively + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 45)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 42)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 45)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 44)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 45)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 43)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, 45)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, 42)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 45)); + + // Lock shared with upgrade to exclusive (POSIX) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, 42)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, 42)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 42)); + + // Lock exclusive with downgrade to shared (POSIX) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, 42)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, 42)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, 42)); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, c_file)); + CLEANUP_CEPH(); +} + +/* Locking in different threads */ + +// Used by ConcurrentLocking test +struct str_ConcurrentLocking { + const char *file; + struct ceph_mount_info *cmount; // !NULL if shared + sem_t sem[2]; + sem_t semReply[2]; + void sem_init(int pshared) { + ASSERT_EQ(0, ::sem_init(&sem[0], pshared, 0)); + ASSERT_EQ(0, ::sem_init(&sem[1], pshared, 0)); + ASSERT_EQ(0, ::sem_init(&semReply[0], pshared, 0)); + ASSERT_EQ(0, ::sem_init(&semReply[1], pshared, 0)); + } + void sem_destroy() { + ASSERT_EQ(0, ::sem_destroy(&sem[0])); + ASSERT_EQ(0, ::sem_destroy(&sem[1])); + ASSERT_EQ(0, ::sem_destroy(&semReply[0])); + ASSERT_EQ(0, ::sem_destroy(&semReply[1])); + } +}; + +// Wakeup main (for (N) steps) +#define PING_MAIN(n) ASSERT_EQ(0, sem_post(&s.sem[n%2])) +// Wait for main to wake us up (for (RN) steps) +#define WAIT_MAIN(n) \ + ASSERT_EQ(0, sem_timedwait(&s.semReply[n%2], abstime(ts, waitSlowMs))) + +// Wakeup worker (for (RN) steps) +#define PING_WORKER(n) ASSERT_EQ(0, sem_post(&s.semReply[n%2])) +// Wait for worker to wake us up (for (N) steps) +#define WAIT_WORKER(n) \ + ASSERT_EQ(0, sem_timedwait(&s.sem[n%2], abstime(ts, waitSlowMs))) +// Worker shall not wake us up (for (N) steps) +#define NOT_WAIT_WORKER(n) \ + ASSERT_EQ(-1, sem_timedwait(&s.sem[n%2], abstime(ts, waitMs))) + +// Do twice an operation +#define TWICE(EXPR) do { \ + EXPR; \ + EXPR; \ + } while(0) + +/* Locking in different threads */ + +// Used by ConcurrentLocking test +static void thread_ConcurrentLocking(str_ConcurrentLocking& s) { + struct ceph_mount_info *const cmount = s.cmount; + struct timespec ts; + + const int fd = ceph_open(cmount, s.file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + PING_MAIN(1); // (1) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, ceph_pthread_self())); + PING_MAIN(2); // (2) + + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + PING_MAIN(3); // (3) + + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, ceph_pthread_self())); + PING_MAIN(4); // (4) + + WAIT_MAIN(1); // (R1) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + PING_MAIN(5); // (5) + + WAIT_MAIN(2); // (R2) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, ceph_pthread_self())); + PING_MAIN(6); // (6) + + WAIT_MAIN(3); // (R3) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + PING_MAIN(7); // (7) +} + +// Used by ConcurrentLocking test +static void* thread_ConcurrentLocking_(void *arg) { + str_ConcurrentLocking *const s = + reinterpret_cast<str_ConcurrentLocking*>(arg); + thread_ConcurrentLocking(*s); + return NULL; +} + +TEST(LibCephFS, ConcurrentLocking) { + const pid_t mypid = getpid(); + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + char c_file[1024]; + sprintf(c_file, "/flock_test_%d", mypid); + const int fd = ceph_open(cmount, c_file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + + // Lock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, ceph_pthread_self())); + + // Start locker thread + pthread_t thread; + struct timespec ts; + str_ConcurrentLocking s = { c_file, cmount }; + s.sem_init(0); + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_ConcurrentLocking_, &s)); + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(1); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + + // Shall have lock + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(3); // (3) + + // Wait for thread to share lock + WAIT_WORKER(4); // (4) + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, ceph_pthread_self())); + + // Wake up thread to unlock shared lock + PING_WORKER(1); // (R1) + WAIT_WORKER(5); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, ceph_pthread_self())); + + // Wake up thread to lock shared lock + PING_WORKER(2); // (R2) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6); // (6) + + // Release lock ; thread will get it + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + WAIT_WORKER(6); // (6) + + // We no longer have the lock + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, ceph_pthread_self())); + + // Wake up thread to unlock exclusive lock + PING_WORKER(3); // (R3) + WAIT_WORKER(7); // (7) + + // We can lock it again + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + + // Cleanup + void *retval = (void*) (uintptr_t) -1; + ASSERT_EQ(0, pthread_join(thread, &retval)); + ASSERT_EQ(NULL, retval); + s.sem_destroy(); + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, c_file)); + CLEANUP_CEPH(); +} + +TEST(LibCephFS, ThreesomeLocking) { + const pid_t mypid = getpid(); + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + char c_file[1024]; + sprintf(c_file, "/flock_test_%d", mypid); + const int fd = ceph_open(cmount, c_file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + + // Lock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, ceph_pthread_self())); + + // Start locker thread + pthread_t thread[2]; + struct timespec ts; + str_ConcurrentLocking s = { c_file, cmount }; + s.sem_init(0); + ASSERT_EQ(0, pthread_create(&thread[0], NULL, thread_ConcurrentLocking_, &s)); + ASSERT_EQ(0, pthread_create(&thread[1], NULL, thread_ConcurrentLocking_, &s)); + // Synchronization point with thread (failure: thread is dead) + TWICE(WAIT_WORKER(1)); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + + // Shall have lock + TWICE(// Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(3)); // (3) + + // Wait for thread to share lock + TWICE(WAIT_WORKER(4)); // (4) + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, ceph_pthread_self())); + + // Wake up thread to unlock shared lock + TWICE(PING_WORKER(1); // (R1) + WAIT_WORKER(5)); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, ceph_pthread_self())); + + TWICE( // Wake up thread to lock shared lock + PING_WORKER(2); // (R2) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6)); // (6) + + // Release lock ; thread will get it + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + TWICE(WAIT_WORKER(6); // (6) + + // We no longer have the lock + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, ceph_pthread_self())); + + // Wake up thread to unlock exclusive lock + PING_WORKER(3); // (R3) + WAIT_WORKER(7); // (7) + ); + + // We can lock it again + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, ceph_pthread_self())); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, ceph_pthread_self())); + + // Cleanup + void *retval = (void*) (uintptr_t) -1; + ASSERT_EQ(0, pthread_join(thread[0], &retval)); + ASSERT_EQ(NULL, retval); + ASSERT_EQ(0, pthread_join(thread[1], &retval)); + ASSERT_EQ(NULL, retval); + s.sem_destroy(); + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, c_file)); + CLEANUP_CEPH(); +} + +/* Locking in different processes */ + +#define PROCESS_SLOW_MS() \ + static const long waitMs = 100; \ + (void) waitMs + +// Used by ConcurrentLocking test +static void process_ConcurrentLocking(str_ConcurrentLocking& s) { + const pid_t mypid = getpid(); + PROCESS_SLOW_MS(); + + struct ceph_mount_info *cmount = NULL; + struct timespec ts; + + STARTUP_CEPH(); + s.cmount = cmount; + + const int fd = ceph_open(cmount, s.file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + WAIT_MAIN(1); // (R1) + + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + PING_MAIN(1); // (1) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, mypid)); + PING_MAIN(2); // (2) + + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + PING_MAIN(3); // (3) + + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH, mypid)); + PING_MAIN(4); // (4) + + WAIT_MAIN(2); // (R2) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + PING_MAIN(5); // (5) + + WAIT_MAIN(3); // (R3) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, mypid)); + PING_MAIN(6); // (6) + + WAIT_MAIN(4); // (R4) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + PING_MAIN(7); // (7) + + CLEANUP_CEPH(); + + s.sem_destroy(); + exit(EXIT_SUCCESS); +} + +#ifndef _WIN32 +// Disabled because of fork() issues (http://tracker.ceph.com/issues/16556) +TEST(LibCephFS, DISABLED_InterProcessLocking) { + PROCESS_SLOW_MS(); + // Process synchronization + char c_file[1024]; + const pid_t mypid = getpid(); + sprintf(c_file, "/flock_test_%d", mypid); + + // Note: the semaphores MUST be on a shared memory segment + str_ConcurrentLocking *const shs = + reinterpret_cast<str_ConcurrentLocking*> + (mmap(0, sizeof(*shs), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, + -1, 0)); + str_ConcurrentLocking &s = *shs; + s.file = c_file; + s.sem_init(1); + + // Start locker process + const pid_t pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + process_ConcurrentLocking(s); + exit(EXIT_FAILURE); + } + + struct timespec ts; + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + const int fd = ceph_open(cmount, c_file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + + // Lock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, mypid)); + + // Synchronization point with process (failure: process is dead) + PING_WORKER(1); // (R1) + WAIT_WORKER(1); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + + // Shall have lock + // Synchronization point with process (failure: process is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with process (failure: process is dead) + WAIT_WORKER(3); // (3) + + // Wait for process to share lock + WAIT_WORKER(4); // (4) + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, mypid)); + + // Wake up process to unlock shared lock + PING_WORKER(2); // (R2) + WAIT_WORKER(5); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, mypid)); + + // Wake up process to lock shared lock + PING_WORKER(3); // (R3) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6); // (6) + + // Release lock ; process will get it + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + WAIT_WORKER(6); // (6) + + // We no longer have the lock + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, mypid)); + + // Wake up process to unlock exclusive lock + PING_WORKER(4); // (R4) + WAIT_WORKER(7); // (7) + + // We can lock it again + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + + // Wait pid + int status; + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_EQ(EXIT_SUCCESS, status); + + // Cleanup + s.sem_destroy(); + ASSERT_EQ(0, munmap(shs, sizeof(*shs))); + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, c_file)); + CLEANUP_CEPH(); +} +#endif + +#ifndef _WIN32 +// Disabled because of fork() issues (http://tracker.ceph.com/issues/16556) +TEST(LibCephFS, DISABLED_ThreesomeInterProcessLocking) { + PROCESS_SLOW_MS(); + // Process synchronization + char c_file[1024]; + const pid_t mypid = getpid(); + sprintf(c_file, "/flock_test_%d", mypid); + + // Note: the semaphores MUST be on a shared memory segment + str_ConcurrentLocking *const shs = + reinterpret_cast<str_ConcurrentLocking*> + (mmap(0, sizeof(*shs), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, + -1, 0)); + str_ConcurrentLocking &s = *shs; + s.file = c_file; + s.sem_init(1); + + // Start locker processes + pid_t pid[2]; + pid[0] = fork(); + ASSERT_GE(pid[0], 0); + if (pid[0] == 0) { + process_ConcurrentLocking(s); + exit(EXIT_FAILURE); + } + pid[1] = fork(); + ASSERT_GE(pid[1], 0); + if (pid[1] == 0) { + process_ConcurrentLocking(s); + exit(EXIT_FAILURE); + } + + struct timespec ts; + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + const int fd = ceph_open(cmount, c_file, O_RDWR | O_CREAT, fileMode); + ASSERT_GE(fd, 0); + + // Lock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, mypid)); + + // Synchronization point with process (failure: process is dead) + TWICE(PING_WORKER(1)); // (R1) + TWICE(WAIT_WORKER(1)); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + + // Shall have lock + TWICE(// Synchronization point with process (failure: process is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with process (failure: process is dead) + WAIT_WORKER(3)); // (3) + + // Wait for process to share lock + TWICE(WAIT_WORKER(4)); // (4) + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, mypid)); + + // Wake up process to unlock shared lock + TWICE(PING_WORKER(2); // (R2) + WAIT_WORKER(5)); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX, mypid)); + + TWICE( // Wake up process to lock shared lock + PING_WORKER(3); // (R3) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6)); // (6) + + // Release lock ; process will get it + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + TWICE(WAIT_WORKER(6); // (6) + + // We no longer have the lock + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + ASSERT_EQ(-CEPHFS_EWOULDBLOCK, + ceph_flock(cmount, fd, LOCK_SH | LOCK_NB, mypid)); + + // Wake up process to unlock exclusive lock + PING_WORKER(4); // (R4) + WAIT_WORKER(7); // (7) + ); + + // We can lock it again + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_EX | LOCK_NB, mypid)); + ASSERT_EQ(0, ceph_flock(cmount, fd, LOCK_UN, mypid)); + + // Wait pids + int status; + ASSERT_EQ(pid[0], waitpid(pid[0], &status, 0)); + ASSERT_EQ(EXIT_SUCCESS, status); + ASSERT_EQ(pid[1], waitpid(pid[1], &status, 0)); + ASSERT_EQ(EXIT_SUCCESS, status); + + // Cleanup + s.sem_destroy(); + ASSERT_EQ(0, munmap(shs, sizeof(*shs))); + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, c_file)); + CLEANUP_CEPH(); +} +#endif diff --git a/src/test/libcephfs/lazyio.cc b/src/test/libcephfs/lazyio.cc new file mode 100644 index 000000000..0c17f1f2b --- /dev/null +++ b/src/test/libcephfs/lazyio.cc @@ -0,0 +1,356 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat Ltd + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/rados/librados.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#if defined(__linux__) +#include <sys/xattr.h> +#endif + +rados_t cluster; + +TEST(LibCephFS, LazyIOOneWriterMulipleReaders) { + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo.%d", getpid()); + + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + + int fdb = ceph_open(cb, name, O_RDONLY, 0644); + ASSERT_LE(0, fdb); + + ASSERT_EQ(0, ceph_lazyio(ca, fda, 1)); + ASSERT_EQ(0, ceph_lazyio(cb, fdb, 1)); + + char out_buf[] = "fooooooooo"; + + /* Client a issues a write and propagates/flushes the buffer */ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(ca, fda, out_buf, sizeof(out_buf), 0)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + /* Client a issues a write and propagates/flushes the buffer */ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(ca, fda, out_buf, sizeof(out_buf), 10)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + char in_buf[40]; + /* Calling ceph_lazyio_synchronize here will invalidate client b's cache and hence enable client a to fetch the propagated write of client a in the subsequent read */ + ASSERT_EQ(0, ceph_lazyio_synchronize(cb, fdb, 0, 0)); + ASSERT_EQ(ceph_read(cb, fdb, in_buf, sizeof(in_buf), 0), 2*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooo"); + + /* Client a does not need to call ceph_lazyio_synchronize here because it is the latest writer and fda holds the updated inode*/ + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), 2*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooo"); + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +TEST(LibCephFS, LazyIOMultipleWritersMulipleReaders) { + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo2.%d", getpid()); + + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + + int fdb = ceph_open(cb, name, O_RDWR, 0644); + ASSERT_LE(0, fdb); + + ASSERT_EQ(0, ceph_lazyio(ca, fda, 1)); + ASSERT_EQ(0, ceph_lazyio(cb, fdb, 1)); + + char out_buf[] = "fooooooooo"; + /* Client a issues a write and propagates/flushes the buffer */ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(ca, fda, out_buf, sizeof(out_buf), 0)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + /* Client b issues a write and propagates/flushes the buffer*/ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(cb, fdb, out_buf, sizeof(out_buf), 10)); + ASSERT_EQ(0, ceph_lazyio_propagate(cb, fdb, 0, 0)); + + char in_buf[40]; + /* Calling ceph_lazyio_synchronize here will invalidate client a's cache and hence enable client a to fetch the propagated writes of client b in the subsequent read */ + ASSERT_EQ(0, ceph_lazyio_synchronize(ca, fda, 0, 0)); + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), 2*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooo"); + + /* Client b does not need to call ceph_lazyio_synchronize here because it is the latest writer and the writes before it have already been propagated*/ + ASSERT_EQ(ceph_read(cb, fdb, in_buf, sizeof(in_buf), 0), 2*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooo"); + + /* Client a issues a write */ + char wait_out_buf[] = "foobarbars"; + ASSERT_EQ((int)sizeof(wait_out_buf), ceph_write(ca, fda, wait_out_buf, sizeof(wait_out_buf), 20)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + /* Client a does not need to call ceph_lazyio_synchronize here because it is the latest writer and the writes before it have already been propagated*/ + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), (2*(strlen(out_buf)))+strlen(wait_out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooofoobarbars"); + + /* Calling ceph_lazyio_synchronize here will invalidate client b's cache and hence enable client a to fetch the propagated write of client a in the subsequent read */ + ASSERT_EQ(0, ceph_lazyio_synchronize(cb, fdb, 0, 0)); + ASSERT_EQ(ceph_read(cb, fdb, in_buf, sizeof(in_buf), 0), (2*(strlen(out_buf)))+strlen(wait_out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooofoobarbars"); + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +TEST(LibCephFS, LazyIOMultipleWritersOneReader) { + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo3.%d", getpid()); + + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + + int fdb = ceph_open(cb, name, O_RDWR, 0644); + ASSERT_LE(0, fdb); + + ASSERT_EQ(0, ceph_lazyio(ca, fda, 1)); + ASSERT_EQ(0, ceph_lazyio(cb, fdb, 1)); + + char out_buf[] = "fooooooooo"; + /* Client a issues a write and propagates/flushes the buffer */ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(ca, fda, out_buf, sizeof(out_buf), 0)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + /* Client b issues a write and propagates/flushes the buffer*/ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(cb, fdb, out_buf, sizeof(out_buf), 10)); + ASSERT_EQ(0, ceph_lazyio_propagate(cb, fdb, 0, 0)); + + char in_buf[40]; + /* Client a reads the file and verifies that it only reads it's propagated writes and not Client b's*/ + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooo"); + + /* Client a reads the file again, this time with a lazyio_synchronize to check if the cache gets invalidated and data is refetched i.e all the propagated writes are being read*/ + ASSERT_EQ(0, ceph_lazyio_synchronize(ca, fda, 0, 0)); + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), 2*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooo"); + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +TEST(LibCephFS, LazyIOSynchronizeFlush) { + /* Test to make sure lazyio_synchronize flushes dirty buffers */ + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo4.%d", getpid()); + + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + + int fdb = ceph_open(cb, name, O_RDWR, 0644); + ASSERT_LE(0, fdb); + + ASSERT_EQ(0, ceph_lazyio(ca, fda, 1)); + ASSERT_EQ(0, ceph_lazyio(cb, fdb, 1)); + + char out_buf[] = "fooooooooo"; + + /* Client a issues a write and propagates it*/ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(ca, fda, out_buf, sizeof(out_buf), 0)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + /* Client b issues writes and without lazyio_propagate*/ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(cb, fdb, out_buf, sizeof(out_buf), 10)); + ASSERT_EQ((int)sizeof(out_buf), ceph_write(cb, fdb, out_buf, sizeof(out_buf), 20)); + + char in_buf[40]; + /* Calling ceph_lazyio_synchronize here will first flush the possibly pending buffered write of client b and invalidate client b's cache and hence enable client b to fetch all the propagated writes */ + ASSERT_EQ(0, ceph_lazyio_synchronize(cb, fdb, 0, 0)); + ASSERT_EQ(ceph_read(cb, fdb, in_buf, sizeof(in_buf), 0), 3*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooofooooooooo"); + + /* Required to call ceph_lazyio_synchronize here since client b is the latest writer and client a is out of sync with updated file*/ + ASSERT_EQ(0, ceph_lazyio_synchronize(ca, fda, 0, 0)); + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), 3*strlen(out_buf)+1); + ASSERT_STREQ(in_buf, "fooooooooofooooooooofooooooooo"); + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +TEST(LibCephFS, WithoutandWithLazyIO) { + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo5.%d", getpid()); + + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + + int fdb = ceph_open(cb, name, O_RDWR, 0644); + ASSERT_LE(0, fdb); + + char out_buf_w[] = "1234567890"; + /* Doing some non lazyio writes and read*/ + ASSERT_EQ((int)sizeof(out_buf_w), ceph_write(ca, fda, out_buf_w, sizeof(out_buf_w), 0)); + + ASSERT_EQ((int)sizeof(out_buf_w), ceph_write(cb, fdb, out_buf_w, sizeof(out_buf_w), 10)); + + char in_buf_w[30]; + ASSERT_EQ(ceph_read(ca, fda, in_buf_w, sizeof(in_buf_w), 0), 2*strlen(out_buf_w)+1); + + /* Enable lazyio*/ + ASSERT_EQ(0, ceph_lazyio(ca, fda, 1)); + ASSERT_EQ(0, ceph_lazyio(cb, fdb, 1)); + + char out_buf[] = "fooooooooo"; + + /* Client a issues a write and propagates/flushes the buffer*/ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(ca, fda, out_buf, sizeof(out_buf), 20)); + ASSERT_EQ(0, ceph_lazyio_propagate(ca, fda, 0, 0)); + + /* Client b issues a write and propagates/flushes the buffer*/ + ASSERT_EQ((int)sizeof(out_buf), ceph_write(cb, fdb, out_buf, sizeof(out_buf), 30)); + ASSERT_EQ(0, ceph_lazyio_propagate(cb, fdb, 0, 0)); + + char in_buf[50]; + /* Calling ceph_lazyio_synchronize here will invalidate client a's cache and hence enable client a to fetch the propagated writes of client b in the subsequent read */ + ASSERT_EQ(0, ceph_lazyio_synchronize(ca, fda, 0, 0)); + ASSERT_EQ(ceph_read(ca, fda, in_buf, sizeof(in_buf), 0), (2*(strlen(out_buf)))+(2*(strlen(out_buf_w)))+1); + ASSERT_STREQ(in_buf, "12345678901234567890fooooooooofooooooooo"); + + /* Client b does not need to call ceph_lazyio_synchronize here because it is the latest writer and the writes before it have already been propagated*/ + ASSERT_EQ(ceph_read(cb, fdb, in_buf, sizeof(in_buf), 0), (2*(strlen(out_buf)))+(2*(strlen(out_buf_w)))+1); + ASSERT_STREQ(in_buf, "12345678901234567890fooooooooofooooooooo"); + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +static int update_root_mode() +{ + struct ceph_mount_info *admin; + int r = ceph_create(&admin, NULL); + if (r < 0) + return r; + ceph_conf_read_file(admin, NULL); + ceph_conf_parse_env(admin, NULL); + ceph_conf_set(admin, "client_permissions", "false"); + r = ceph_mount(admin, "/"); + if (r < 0) + goto out; + r = ceph_chmod(admin, "/", 0777); +out: + ceph_shutdown(admin); + return r; +} + +int main(int argc, char **argv) +{ + int r = update_root_mode(); + if (r < 0) + exit(1); + + ::testing::InitGoogleTest(&argc, argv); + + srand(getpid()); + + r = rados_create(&cluster, NULL); + if (r < 0) + exit(1); + + r = rados_conf_read_file(cluster, NULL); + if (r < 0) + exit(1); + + rados_conf_parse_env(cluster, NULL); + r = rados_connect(cluster); + if (r < 0) + exit(1); + + r = RUN_ALL_TESTS(); + + rados_shutdown(cluster); + + return r; +} diff --git a/src/test/libcephfs/main.cc b/src/test/libcephfs/main.cc new file mode 100644 index 000000000..5def83c43 --- /dev/null +++ b/src/test/libcephfs/main.cc @@ -0,0 +1,50 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * Copyright (C) 2016 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" + +static int update_root_mode() +{ + struct ceph_mount_info *admin; + int r = ceph_create(&admin, NULL); + if (r < 0) + return r; + ceph_conf_read_file(admin, NULL); + ceph_conf_parse_env(admin, NULL); + ceph_conf_set(admin, "client_permissions", "false"); + r = ceph_mount(admin, "/"); + if (r < 0) + goto out; + r = ceph_chmod(admin, "/", 01777); +out: + ceph_shutdown(admin); + return r; +} + + +int main(int argc, char **argv) +{ + int r = update_root_mode(); + if (r < 0) + exit(1); + + ::testing::InitGoogleTest(&argc, argv); + + srand(getpid()); + + return RUN_ALL_TESTS(); +} diff --git a/src/test/libcephfs/monconfig.cc b/src/test/libcephfs/monconfig.cc new file mode 100644 index 000000000..178278c6b --- /dev/null +++ b/src/test/libcephfs/monconfig.cc @@ -0,0 +1,101 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "common/ceph_context.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +class MonConfig : public ::testing::Test +{ + protected: + struct ceph_mount_info *ca; + + void SetUp() override { + ASSERT_EQ(0, ceph_create(&ca, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(ca, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + } + + void TearDown() override { + ceph_shutdown(ca); + } + + // Helper to remove/unset all possible mon information from ConfigProxy + void clear_mon_config(CephContext *cct) { + auto& conf = cct->_conf; + // Clear safe_to_start_threads, allowing updates to config values + conf._clear_safe_to_start_threads(); + ASSERT_EQ(0, conf.set_val("monmap", "", nullptr)); + ASSERT_EQ(0, conf.set_val("mon_host", "", nullptr)); + ASSERT_EQ(0, conf.set_val("mon_dns_srv_name", "", nullptr)); + conf.set_safe_to_start_threads(); + } + + // Helper to test basic operation on a mount + void use_mount(struct ceph_mount_info *mnt, std::string name_prefix) { + char name[20]; + snprintf(name, sizeof(name), "%s.%d", name_prefix.c_str(), getpid()); + int fd = ceph_open(mnt, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fd); + + ceph_close(mnt, fd); + } +}; + +TEST_F(MonConfig, MonAddrsMissing) { + CephContext *cct; + + // Test mount failure when there is no known mon config source + cct = ceph_get_mount_context(ca); + ASSERT_NE(nullptr, cct); + clear_mon_config(cct); + + ASSERT_EQ(-CEPHFS_ENOENT, ceph_mount(ca, NULL)); +} + +TEST_F(MonConfig, MonAddrsInConfigProxy) { + // Test a successful mount with default mon config source in ConfigProxy + ASSERT_EQ(0, ceph_mount(ca, NULL)); + + use_mount(ca, "foo"); +} + +TEST_F(MonConfig, MonAddrsInCct) { + struct ceph_mount_info *cb; + CephContext *cct; + + // Perform mount to bootstrap mon addrs in CephContext + ASSERT_EQ(0, ceph_mount(ca, NULL)); + + // Reuse bootstrapped CephContext, clearing ConfigProxy mon addr sources + cct = ceph_get_mount_context(ca); + ASSERT_NE(nullptr, cct); + clear_mon_config(cct); + ASSERT_EQ(0, ceph_create_with_context(&cb, cct)); + + // Test a successful mount with only mon values in CephContext + ASSERT_EQ(0, ceph_mount(cb, NULL)); + + use_mount(ca, "bar"); + use_mount(cb, "bar"); + + ceph_shutdown(cb); +} diff --git a/src/test/libcephfs/multiclient.cc b/src/test/libcephfs/multiclient.cc new file mode 100644 index 000000000..c9fc038bc --- /dev/null +++ b/src/test/libcephfs/multiclient.cc @@ -0,0 +1,180 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <thread> +#ifdef __linux__ +#include <sys/xattr.h> +#endif + +TEST(LibCephFS, MulticlientSimple) { + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo.%d", getpid()); + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + int fdb = ceph_open(cb, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fdb); + + char bufa[4] = "foo"; + char bufb[4]; + + for (int i=0; i<10; i++) { + strcpy(bufa, "foo"); + ASSERT_EQ((int)sizeof(bufa), ceph_write(ca, fda, bufa, sizeof(bufa), i*6)); + ASSERT_EQ((int)sizeof(bufa), ceph_read(cb, fdb, bufb, sizeof(bufa), i*6)); + ASSERT_EQ(0, memcmp(bufa, bufb, sizeof(bufa))); + strcpy(bufb, "bar"); + ASSERT_EQ((int)sizeof(bufb), ceph_write(cb, fdb, bufb, sizeof(bufb), i*6+3)); + ASSERT_EQ((int)sizeof(bufb), ceph_read(ca, fda, bufa, sizeof(bufb), i*6+3)); + ASSERT_EQ(0, memcmp(bufa, bufb, sizeof(bufa))); + } + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +TEST(LibCephFS, MulticlientHoleEOF) { + struct ceph_mount_info *ca, *cb; + ASSERT_EQ(ceph_create(&ca, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(ca, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(ca, NULL)); + ASSERT_EQ(ceph_mount(ca, NULL), 0); + + ASSERT_EQ(ceph_create(&cb, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cb, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cb, NULL)); + ASSERT_EQ(ceph_mount(cb, NULL), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo.%d", getpid()); + int fda = ceph_open(ca, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fda); + int fdb = ceph_open(cb, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fdb); + + ASSERT_EQ(3, ceph_write(ca, fda, "foo", 3, 0)); + ASSERT_EQ(0, ceph_ftruncate(ca, fda, 1000000)); + + char buf[4]; + ASSERT_EQ(2, ceph_read(cb, fdb, buf, sizeof(buf), 1000000-2)); + ASSERT_EQ(0, buf[0]); + ASSERT_EQ(0, buf[1]); + + ceph_close(ca, fda); + ceph_close(cb, fdb); + + ceph_shutdown(ca); + ceph_shutdown(cb); +} + +static void write_func(bool *stop) +{ + 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, "/"), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo.%d", getpid()); + int fd = ceph_open(cmount, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fd); + + int buf_size = 4096; + char *buf = (char *)malloc(buf_size); + if (!buf) { + *stop = true; + printf("write_func failed to allocate buffer!"); + return; + } + memset(buf, 1, buf_size); + + while (!(*stop)) { + int i; + + // truncate the file size to 4096 will set the max_size to 4MB. + ASSERT_EQ(0, ceph_ftruncate(cmount, fd, 4096)); + + // write 4MB + extra 64KB data will make client to trigger to + // call check_cap() to report new size. And if MDS is revoking + // the Fsxrw caps and we are still holding the Fw caps and will + // trigger tracker#57244. + for (i = 0; i < 1040; i++) { + ASSERT_EQ(ceph_write(cmount, fd, buf, buf_size, 0), buf_size); + } + } + + ceph_shutdown(cmount); +} + +static void setattr_func(bool *stop) +{ + 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, "/"), 0); + + char name[20]; + snprintf(name, sizeof(name), "foo.%d", getpid()); + int fd = ceph_open(cmount, name, O_CREAT|O_RDWR, 0644); + ASSERT_LE(0, fd); + + while (!(*stop)) { + // setattr will make the MDS to acquire xlock for the filelock and + // force to revoke caps from clients + struct ceph_statx stx = {.stx_size = 0}; + ASSERT_EQ(ceph_fsetattrx(cmount, fd, &stx, CEPH_SETATTR_SIZE), 0); + } + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, MulticlientRevokeCaps) { + std::thread thread1, thread2; + bool stop = false; + int wait = 60; // in second + + thread1 = std::thread(write_func, &stop); + thread2 = std::thread(setattr_func, &stop); + + printf(" Will run test for %d seconds!\n", wait); + sleep(wait); + stop = true; + + thread1.join(); + thread2.join(); +} diff --git a/src/test/libcephfs/newops.cc b/src/test/libcephfs/newops.cc new file mode 100644 index 000000000..2a4573b9f --- /dev/null +++ b/src/test/libcephfs/newops.cc @@ -0,0 +1,87 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2021 Red Hat Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gtest/gtest-spi.h" +#include "gmock/gmock-matchers.h" +#include "gmock/gmock-more-matchers.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "mds/mdstypes.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +#include <fmt/format.h> +#include <map> +#include <vector> +#include <thread> +#include <regex> +#include <string> + +using ::testing::AnyOf; +using ::testing::Gt; +using ::testing::Eq; +using namespace std; + +/* + * Test this with different ceph versions + */ + +TEST(LibCephFS, NewOPs) +{ + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + const char *test_path = "test_newops_dir"; + + ASSERT_EQ(0, ceph_mkdirs(cmount, test_path, 0777)); + + { + char value[1024] = ""; + int r = ceph_getxattr(cmount, test_path, "ceph.dir.pin.random", (void*)value, sizeof(value)); + // Clients will return -CEPHFS_ENODATA if new getvxattr op not support yet. + EXPECT_THAT(r, AnyOf(Gt(0), Eq(-CEPHFS_ENODATA))); + } + + { + double val = (double)1.0/(double)128.0; + std::stringstream ss; + ss << val; + int r = ceph_setxattr(cmount, test_path, "ceph.dir.pin.random", (void*)ss.str().c_str(), strlen(ss.str().c_str()), XATTR_CREATE); + // Old cephs will return -CEPHFS_EINVAL if not support "ceph.dir.pin.random" yet. + EXPECT_THAT(r, AnyOf(Eq(0), Eq(-CEPHFS_EINVAL))); + + char value[1024] = ""; + r = ceph_getxattr(cmount, test_path, "ceph.dir.pin.random", (void*)value, sizeof(value)); + // Clients will return -CEPHFS_ENODATA if new getvxattr op not support yet. + EXPECT_THAT(r, AnyOf(Gt(0), Eq(-CEPHFS_ENODATA))); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, test_path)); + + ceph_shutdown(cmount); +} diff --git a/src/test/libcephfs/quota.cc b/src/test/libcephfs/quota.cc new file mode 100644 index 000000000..00afb3d09 --- /dev/null +++ b/src/test/libcephfs/quota.cc @@ -0,0 +1,167 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * Copyright (C) 2022 Red Hat, Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/compat.h" +#include "gtest/gtest.h" +#include "include/cephfs/libcephfs.h" +#include "mds/mdstypes.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +TEST(LibCephFS, SnapQuota) { + 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_snap_dir_quota_xattr[256]; + char test_snap_subdir_quota_xattr[256]; + char test_snap_subdir_noquota_xattr[256]; + char xattrk[128]; + char xattrv[128]; + char c_temp[PATH_MAX]; + char gxattrv[128]; + int xbuflen = sizeof(gxattrv); + pid_t mypid = getpid(); + + // create dir and set quota + sprintf(test_snap_dir_quota_xattr, "test_snap_dir_quota_xattr_%d", mypid); + ASSERT_EQ(0, ceph_mkdir(cmount, test_snap_dir_quota_xattr, 0777)); + + sprintf(xattrk, "ceph.quota.max_bytes"); + sprintf(xattrv, "65536"); + ASSERT_EQ(0, ceph_setxattr(cmount, test_snap_dir_quota_xattr, xattrk, (void *)xattrv, 5, XATTR_CREATE)); + + // create subdir and set quota + sprintf(test_snap_subdir_quota_xattr, "test_snap_dir_quota_xattr_%d/subdir_quota", mypid); + ASSERT_EQ(0, ceph_mkdirs(cmount, test_snap_subdir_quota_xattr, 0777)); + + sprintf(xattrk, "ceph.quota.max_bytes"); + sprintf(xattrv, "32768"); + ASSERT_EQ(0, ceph_setxattr(cmount, test_snap_subdir_quota_xattr, xattrk, (void *)xattrv, 5, XATTR_CREATE)); + + // create subdir with no quota + sprintf(test_snap_subdir_noquota_xattr, "test_snap_dir_quota_xattr_%d/subdir_noquota", mypid); + ASSERT_EQ(0, ceph_mkdirs(cmount, test_snap_subdir_noquota_xattr, 0777)); + + // snapshot dir + sprintf(c_temp, "/.snap/test_snap_dir_quota_xattr_snap_%d", mypid); + ASSERT_EQ(0, ceph_mkdirs(cmount, c_temp, 0777)); + + // check dir quota under snap + sprintf(c_temp, "/.snap/test_snap_dir_quota_xattr_snap_%d/test_snap_dir_quota_xattr_%d", mypid, mypid); + int alen = ceph_getxattr(cmount, c_temp, "ceph.quota.max_bytes", (void *)gxattrv, xbuflen); + ASSERT_LT(0, alen); + ASSERT_LT(alen, xbuflen); + gxattrv[alen] = '\0'; + ASSERT_STREQ(gxattrv, "65536"); + + // check subdir quota under snap + sprintf(c_temp, "/.snap/test_snap_dir_quota_xattr_snap_%d/test_snap_dir_quota_xattr_%d/subdir_quota", mypid, mypid); + alen = ceph_getxattr(cmount, c_temp, "ceph.quota.max_bytes", (void *)gxattrv, xbuflen); + ASSERT_LT(0, alen); + ASSERT_LT(alen, xbuflen); + gxattrv[alen] = '\0'; + ASSERT_STREQ(gxattrv, "32768"); + + // ensure subdir noquota xattr under snap + sprintf(c_temp, "/.snap/test_snap_dir_quota_xattr_snap_%d/test_snap_dir_quota_xattr_%d/subdir_noquota", mypid, mypid); + EXPECT_EQ(-CEPHFS_ENODATA, ceph_getxattr(cmount, c_temp, "ceph.quota.max_bytes", (void *)gxattrv, xbuflen)); + + // listxattr() shouldn't return ceph.quota.max_bytes vxattr + sprintf(c_temp, "/.snap/test_snap_dir_quota_xattr_snap_%d/test_snap_dir_quota_xattr_%d", mypid, mypid); + char xattrlist[512]; + int len = ceph_listxattr(cmount, c_temp, xattrlist, sizeof(xattrlist)); + ASSERT_GE(sizeof(xattrlist), (size_t)len); + char *p = xattrlist; + int found = 0; + while (len > 0) { + if (strcmp(p, "ceph.quota.max_bytes") == 0) + found++; + len -= strlen(p) + 1; + p += strlen(p) + 1; + } + ASSERT_EQ(found, 0); + + ceph_shutdown(cmount); +} + +void statfs_quota_size_check(struct ceph_mount_info *cmount, const char *path, + int blocks, int bsize) +{ + struct statvfs stvfs; + + ASSERT_EQ(0, ceph_statfs(cmount, path, &stvfs)); + ASSERT_EQ(blocks, stvfs.f_blocks); + ASSERT_EQ(bsize, stvfs.f_bsize); + ASSERT_EQ(bsize, stvfs.f_frsize); +} + +TEST(LibCephFS, QuotaRealm) { + struct ceph_mount_info *cmount, *pmount1, *pmount2; + char test_quota_realm_pdir[128]; + char test_quota_realm_cdir[256]; + char xattrk[32]; + char xattrv[16]; + + 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); + + pid_t mypid = getpid(); + + // create parent directory and set quota size + sprintf(test_quota_realm_pdir, "/test_quota_realm_pdir_%d", mypid); + ASSERT_EQ(0, ceph_mkdir(cmount, test_quota_realm_pdir, 0777)); + sprintf(xattrk, "ceph.quota.max_bytes"); + sprintf(xattrv, "8388608"); // 8MB + ASSERT_EQ(0, ceph_setxattr(cmount, test_quota_realm_pdir, xattrk, (void *)xattrv, 7, XATTR_CREATE)); + + // create child directory and set quota file + sprintf(test_quota_realm_cdir, "%s/test_quota_realm_cdir", test_quota_realm_pdir); + ASSERT_EQ(0, ceph_mkdir(cmount, test_quota_realm_cdir, 0777)); + sprintf(xattrk, "ceph.quota.max_files"); + sprintf(xattrv, "1024"); // 1K files + ASSERT_EQ(0, ceph_setxattr(cmount, test_quota_realm_cdir, xattrk, (void *)xattrv, 4, XATTR_CREATE)); + + ASSERT_EQ(ceph_create(&pmount1, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(pmount1, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(pmount1, NULL)); + ASSERT_EQ(ceph_mount(pmount1, test_quota_realm_pdir), 0); + statfs_quota_size_check(pmount1, "/", 2, 4194304); // 8MB + + ASSERT_EQ(ceph_create(&pmount2, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(pmount2, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(pmount2, NULL)); + ASSERT_EQ(ceph_mount(pmount2, test_quota_realm_cdir), 0); + statfs_quota_size_check(pmount2, "/", 2, 4194304); // 8MB + + ceph_shutdown(pmount1); + ceph_shutdown(pmount2); + ceph_shutdown(cmount); +} diff --git a/src/test/libcephfs/readdir_r_cb.cc b/src/test/libcephfs/readdir_r_cb.cc new file mode 100644 index 000000000..959c276f1 --- /dev/null +++ b/src/test/libcephfs/readdir_r_cb.cc @@ -0,0 +1,65 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include <errno.h> +#include <fcntl.h> + +TEST(LibCephFS, ReaddirRCB) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + char c_dir[256]; + sprintf(c_dir, "/readdir_r_cb_tests_%d", getpid()); + struct ceph_dir_result *dirp; + ASSERT_EQ(0, ceph_mkdirs(cmount, c_dir, 0777)); + ASSERT_LE(0, ceph_opendir(cmount, c_dir, &dirp)); + + // dir is empty, check that it only contains . and .. + int buflen = 100; + char *buf = new char[buflen]; + // . is 2, .. is 3 (for null terminators) + ASSERT_EQ(5, ceph_getdnames(cmount, dirp, buf, buflen)); + char c_file[256]; + sprintf(c_file, "/readdir_r_cb_tests_%d/foo", getpid()); + int fd = ceph_open(cmount, c_file, O_CREAT, 0777); + ASSERT_LT(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + // check correctness with one entry + ASSERT_LE(0, ceph_closedir(cmount, dirp)); + ASSERT_LE(0, ceph_opendir(cmount, c_dir, &dirp)); + ASSERT_EQ(9, ceph_getdnames(cmount, dirp, buf, buflen)); // ., .., foo + + // check correctness if buffer is too small + ASSERT_LE(0, ceph_closedir(cmount, dirp)); + ASSERT_GE(0, ceph_opendir(cmount, c_dir, &dirp)); + ASSERT_EQ(-CEPHFS_ERANGE, ceph_getdnames(cmount, dirp, buf, 1)); + + //check correctness if it needs to split listing + ASSERT_LE(0, ceph_closedir(cmount, dirp)); + ASSERT_LE(0, ceph_opendir(cmount, c_dir, &dirp)); + ASSERT_EQ(5, ceph_getdnames(cmount, dirp, buf, 6)); + ASSERT_EQ(4, ceph_getdnames(cmount, dirp, buf, 6)); + + // free cmount after finishing testing + ASSERT_LE(0, ceph_closedir(cmount, dirp)); + ASSERT_EQ(0, ceph_unmount(cmount)); + ASSERT_EQ(0, ceph_release(cmount)); +} diff --git a/src/test/libcephfs/reclaim.cc b/src/test/libcephfs/reclaim.cc new file mode 100644 index 000000000..736ed5759 --- /dev/null +++ b/src/test/libcephfs/reclaim.cc @@ -0,0 +1,163 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Tests for Ceph delegation handling + * + * (c) 2017, Jeff Layton <jlayton@redhat.com> + */ + +#include "gtest/gtest.h" +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> +#include <libgen.h> +#include <stdlib.h> + +#ifdef __linux__ +#include <sys/xattr.h> +#include <limits.h> +#endif + +#ifdef __FreeBSD__ +#include <sys/wait.h> +#endif + + +#include <map> +#include <vector> +#include <thread> +#include <atomic> + +#define CEPHFS_RECLAIM_TIMEOUT 60 + +static int dying_client(int argc, char **argv) +{ + struct ceph_mount_info *cmount; + + /* Caller must pass in the uuid */ + if (argc < 2) + return 1; + + if (ceph_create(&cmount, nullptr) != 0) + return 1; + + if (ceph_conf_read_file(cmount, nullptr) != 0) + return 1; + + if (ceph_conf_parse_env(cmount, nullptr) != 0) + return 1; + + if (ceph_init(cmount) != 0) + return 1; + + ceph_set_session_timeout(cmount, CEPHFS_RECLAIM_TIMEOUT); + + if (ceph_start_reclaim(cmount, argv[1], CEPH_RECLAIM_RESET) != -CEPHFS_ENOENT) + return 1; + + ceph_set_uuid(cmount, argv[1]); + + if (ceph_mount(cmount, "/") != 0) + return 1; + + Inode *root, *file; + if (ceph_ll_lookup_root(cmount, &root) != 0) + return 1; + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + if (ceph_ll_create(cmount, root, argv[1], 0666, O_RDWR|O_CREAT|O_EXCL, + &file, &fh, &stx, 0, 0, perms) != 0) + return 1; + + return 0; +} + +TEST(LibCephFS, ReclaimReset) { + pid_t pid; + char uuid[256]; + const char *exe = "/proc/self/exe"; + + sprintf(uuid, "simplereclaim:%x", getpid()); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + errno = 0; + execl(exe, exe, uuid, nullptr); + /* It won't be zero of course, which is sort of the point... */ + ASSERT_EQ(errno, 0); + } + + /* parent - wait for child to exit */ + int ret; + pid_t wp = wait(&ret); + ASSERT_GE(wp, 0); + ASSERT_EQ(WIFEXITED(ret), true); + ASSERT_EQ(WEXITSTATUS(ret), 0); + + struct ceph_mount_info *cmount; + ASSERT_EQ(ceph_create(&cmount, nullptr), 0); + ASSERT_EQ(ceph_conf_read_file(cmount, nullptr), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, nullptr)); + ASSERT_EQ(ceph_init(cmount), 0); + ceph_set_session_timeout(cmount, CEPHFS_RECLAIM_TIMEOUT); + ASSERT_EQ(ceph_start_reclaim(cmount, uuid, CEPH_RECLAIM_RESET), 0); + ceph_set_uuid(cmount, uuid); + ASSERT_EQ(ceph_mount(cmount, "/"), 0); + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + UserPerm *perms = ceph_mount_perms(cmount); + struct ceph_statx stx; + ASSERT_EQ(ceph_ll_lookup(cmount, root, uuid, &file, &stx, 0, 0, perms), 0); + Fh *fh; + ASSERT_EQ(ceph_ll_open(cmount, file, O_WRONLY, &fh, perms), 0); + + ceph_unmount(cmount); + ceph_release(cmount); +} + +static int update_root_mode() +{ + struct ceph_mount_info *admin; + int r = ceph_create(&admin, nullptr); + if (r < 0) + return r; + ceph_conf_read_file(admin, nullptr); + ceph_conf_parse_env(admin, nullptr); + ceph_conf_set(admin, "client_permissions", "false"); + r = ceph_mount(admin, "/"); + if (r < 0) + goto out; + r = ceph_chmod(admin, "/", 01777); +out: + ceph_shutdown(admin); + return r; +} + +int main(int argc, char **argv) +{ + int r = update_root_mode(); + if (r < 0) + exit(1); + + ::testing::InitGoogleTest(&argc, argv); + + if (argc > 1) + return dying_client(argc, argv); + + srand(getpid()); + + return RUN_ALL_TESTS(); +} diff --git a/src/test/libcephfs/recordlock.cc b/src/test/libcephfs/recordlock.cc new file mode 100644 index 000000000..ad108b69c --- /dev/null +++ b/src/test/libcephfs/recordlock.cc @@ -0,0 +1,1105 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * 2016 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <pthread.h> +#include "gtest/gtest.h" +#ifndef GTEST_IS_THREADSAFE +#error "!GTEST_IS_THREADSAFE" +#endif + +#include "include/compat.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include <errno.h> +#include <sys/fcntl.h> +#include <unistd.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> + +#include <stdlib.h> +#include <semaphore.h> +#include <time.h> + +#ifndef _WIN32 +#include <sys/mman.h> +#endif + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#elif __FreeBSD__ +#include <sys/types.h> +#include <sys/wait.h> +#endif + +#include "include/ceph_assert.h" +#include "ceph_pthread_self.h" + +// Startup common: create and mount ceph fs +#define STARTUP_CEPH() do { \ + ASSERT_EQ(0, ceph_create(&cmount, NULL)); \ + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); \ + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); \ + ASSERT_EQ(0, ceph_mount(cmount, NULL)); \ + } while(0) + +// Cleanup common: unmount and release ceph fs +#define CLEANUP_CEPH() do { \ + ASSERT_EQ(0, ceph_unmount(cmount)); \ + ASSERT_EQ(0, ceph_release(cmount)); \ + } while(0) + +static const mode_t fileMode = S_IRWXU | S_IRWXG | S_IRWXO; + +// Default wait time for normal and "slow" operations +// (5" should be enough in case of network congestion) +static const long waitMs = 10; +static const long waitSlowMs = 5000; + +// Get the absolute struct timespec reference from now + 'ms' milliseconds +static const struct timespec* abstime(struct timespec &ts, long ms) { + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ceph_abort(); + } + ts.tv_nsec += ms * 1000000; + ts.tv_sec += ts.tv_nsec / 1000000000; + ts.tv_nsec %= 1000000000; + return &ts; +} + +/* Basic locking */ + +TEST(LibCephFS, BasicRecordLocking) { + struct ceph_mount_info *cmount = NULL; + STARTUP_CEPH(); + + char c_file[1024]; + sprintf(c_file, "recordlock_test_%d", getpid()); + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + int rc; + struct flock lock1, lock2; + UserPerm *perms = ceph_mount_perms(cmount); + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + rc = ceph_ll_create(cmount, root, c_file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, perms); + ASSERT_EQ(rc, 0); + + // write lock twice + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, 42, false)); + + lock2.l_type = F_WRLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 0; + lock2.l_len = 1024; + lock2.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock2, 43, false)); + + // Now try a conflicting read lock + lock2.l_type = F_RDLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 100; + lock2.l_len = 100; + lock2.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock2, 43, false)); + + // Now do a getlk + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_WRLCK); + ASSERT_EQ(lock2.l_start, 0); + ASSERT_EQ(lock2.l_len, 1024); + ASSERT_EQ(lock2.l_pid, getpid()); + + // Extend the range of the write lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 1024; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, 42, false)); + + // Now do a getlk + lock2.l_type = F_RDLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 100; + lock2.l_len = 100; + lock2.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_WRLCK); + ASSERT_EQ(lock2.l_start, 0); + ASSERT_EQ(lock2.l_len, 2048); + ASSERT_EQ(lock2.l_pid, getpid()); + + // Now release part of the range + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 512; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, 42, false)); + + // Now do a getlk to check 1st part + lock2.l_type = F_RDLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 100; + lock2.l_len = 100; + lock2.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_WRLCK); + ASSERT_EQ(lock2.l_start, 0); + ASSERT_EQ(lock2.l_len, 512); + ASSERT_EQ(lock2.l_pid, getpid()); + + // Now do a getlk to check 2nd part + lock2.l_type = F_RDLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 2000; + lock2.l_len = 100; + lock2.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_WRLCK); + ASSERT_EQ(lock2.l_start, 1536); + ASSERT_EQ(lock2.l_len, 512); + ASSERT_EQ(lock2.l_pid, getpid()); + + // Now do a getlk to check released part + lock2.l_type = F_RDLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 512; + lock2.l_len = 1024; + lock2.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_UNLCK); + ASSERT_EQ(lock2.l_start, 512); + ASSERT_EQ(lock2.l_len, 1024); + ASSERT_EQ(lock2.l_pid, getpid()); + + // Now downgrade the 1st part of the lock + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 512; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, 42, false)); + + // Now do a getlk to check 1st part + lock2.l_type = F_WRLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 100; + lock2.l_len = 100; + lock2.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_RDLCK); + ASSERT_EQ(lock2.l_start, 0); + ASSERT_EQ(lock2.l_len, 512); + ASSERT_EQ(lock2.l_pid, getpid()); + + // Now upgrade the 1st part of the lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 512; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, 42, false)); + + // Now do a getlk to check 1st part + lock2.l_type = F_WRLCK; + lock2.l_whence = SEEK_SET; + lock2.l_start = 100; + lock2.l_len = 100; + lock2.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_getlk(cmount, fh, &lock2, 43)); + ASSERT_EQ(lock2.l_type, F_WRLCK); + ASSERT_EQ(lock2.l_start, 0); + ASSERT_EQ(lock2.l_len, 512); + ASSERT_EQ(lock2.l_pid, getpid()); + + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); + ASSERT_EQ(0, ceph_ll_unlink(cmount, root, c_file, perms)); + CLEANUP_CEPH(); +} + +/* Locking in different threads */ + +// Used by ConcurrentLocking test +struct str_ConcurrentRecordLocking { + const char *file; + struct ceph_mount_info *cmount; // !NULL if shared + sem_t sem[2]; + sem_t semReply[2]; + void sem_init(int pshared) { + ASSERT_EQ(0, ::sem_init(&sem[0], pshared, 0)); + ASSERT_EQ(0, ::sem_init(&sem[1], pshared, 0)); + ASSERT_EQ(0, ::sem_init(&semReply[0], pshared, 0)); + ASSERT_EQ(0, ::sem_init(&semReply[1], pshared, 0)); + } + void sem_destroy() { + ASSERT_EQ(0, ::sem_destroy(&sem[0])); + ASSERT_EQ(0, ::sem_destroy(&sem[1])); + ASSERT_EQ(0, ::sem_destroy(&semReply[0])); + ASSERT_EQ(0, ::sem_destroy(&semReply[1])); + } +}; + +// Wakeup main (for (N) steps) +#define PING_MAIN(n) ASSERT_EQ(0, sem_post(&s.sem[n%2])) +// Wait for main to wake us up (for (RN) steps) +#define WAIT_MAIN(n) \ + ASSERT_EQ(0, sem_timedwait(&s.semReply[n%2], abstime(ts, waitSlowMs))) + +// Wakeup worker (for (RN) steps) +#define PING_WORKER(n) ASSERT_EQ(0, sem_post(&s.semReply[n%2])) +// Wait for worker to wake us up (for (N) steps) +#define WAIT_WORKER(n) \ + ASSERT_EQ(0, sem_timedwait(&s.sem[n%2], abstime(ts, waitSlowMs))) +// Worker shall not wake us up (for (N) steps) +#define NOT_WAIT_WORKER(n) \ + ASSERT_EQ(-1, sem_timedwait(&s.sem[n%2], abstime(ts, waitMs))) + +// Do twice an operation +#define TWICE(EXPR) do { \ + EXPR; \ + EXPR; \ + } while(0) + +/* Locking in different threads */ + +// Used by ConcurrentLocking test +static void thread_ConcurrentRecordLocking(str_ConcurrentRecordLocking& s) { + struct ceph_mount_info *const cmount = s.cmount; + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + struct flock lock1; + int rc; + struct timespec ts; + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + rc = ceph_ll_create(cmount, root, s.file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, ceph_mount_perms(cmount)); + ASSERT_EQ(rc, 0); + + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + PING_MAIN(1); // (1) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + PING_MAIN(2); // (2) + + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + PING_MAIN(3); // (3) + + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + PING_MAIN(4); // (4) + + WAIT_MAIN(1); // (R1) + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + PING_MAIN(5); // (5) + + WAIT_MAIN(2); // (R2) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + PING_MAIN(6); // (6) + + WAIT_MAIN(3); // (R3) + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + PING_MAIN(7); // (7) + + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); +} + +// Used by ConcurrentRecordLocking test +static void* thread_ConcurrentRecordLocking_(void *arg) { + str_ConcurrentRecordLocking *const s = + reinterpret_cast<str_ConcurrentRecordLocking*>(arg); + thread_ConcurrentRecordLocking(*s); + return NULL; +} + +TEST(LibCephFS, ConcurrentRecordLocking) { + const pid_t mypid = getpid(); + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + char c_file[1024]; + sprintf(c_file, "recordlock_test_%d", mypid); + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + struct flock lock1; + int rc; + UserPerm *perms = ceph_mount_perms(cmount); + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + rc = ceph_ll_create(cmount, root, c_file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, perms); + ASSERT_EQ(rc, 0); + + // Lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + + // Start locker thread + pthread_t thread; + struct timespec ts; + str_ConcurrentRecordLocking s = { c_file, cmount }; + s.sem_init(0); + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_ConcurrentRecordLocking_, &s)); + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(1); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Shall have lock + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(3); // (3) + + // Wait for thread to share lock + WAIT_WORKER(4); // (4) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Wake up thread to unlock shared lock + PING_WORKER(1); // (R1) + WAIT_WORKER(5); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + + // Wake up thread to lock shared lock + PING_WORKER(2); // (R2) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6); // (6) + + // Release lock ; thread will get it + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + WAIT_WORKER(6); // (6) + + // We no longer have the lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Wake up thread to unlock exclusive lock + PING_WORKER(3); // (R3) + WAIT_WORKER(7); // (7) + + // We can lock it again + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Cleanup + void *retval = (void*) (uintptr_t) -1; + ASSERT_EQ(0, pthread_join(thread, &retval)); + ASSERT_EQ(NULL, retval); + s.sem_destroy(); + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); + ASSERT_EQ(0, ceph_ll_unlink(cmount, root, c_file, perms)); + CLEANUP_CEPH(); +} + +TEST(LibCephFS, ThreesomeRecordLocking) { + const pid_t mypid = getpid(); + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + char c_file[1024]; + sprintf(c_file, "recordlock_test_%d", mypid); + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + struct flock lock1; + int rc; + UserPerm *perms = ceph_mount_perms(cmount); + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + rc = ceph_ll_create(cmount, root, c_file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, perms); + ASSERT_EQ(rc, 0); + + // Lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + + // Start locker thread + pthread_t thread[2]; + struct timespec ts; + str_ConcurrentRecordLocking s = { c_file, cmount }; + s.sem_init(0); + ASSERT_EQ(0, pthread_create(&thread[0], NULL, thread_ConcurrentRecordLocking_, &s)); + ASSERT_EQ(0, pthread_create(&thread[1], NULL, thread_ConcurrentRecordLocking_, &s)); + // Synchronization point with thread (failure: thread is dead) + TWICE(WAIT_WORKER(1)); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Shall have lock + TWICE(// Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with thread (failure: thread is dead) + WAIT_WORKER(3)); // (3) + + // Wait for thread to share lock + TWICE(WAIT_WORKER(4)); // (4) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Wake up thread to unlock shared lock + TWICE(PING_WORKER(1); // (R1) + WAIT_WORKER(5)); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), true)); + + TWICE( // Wake up thread to lock shared lock + PING_WORKER(2); // (R2) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6)); // (6) + + // Release lock ; thread will get it + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + TWICE(WAIT_WORKER(6); // (6) + + // We no longer have the lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Wake up thread to unlock exclusive lock + PING_WORKER(3); // (R3) + WAIT_WORKER(7); // (7) + ); + + // We can lock it again + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Cleanup + void *retval = (void*) (uintptr_t) -1; + ASSERT_EQ(0, pthread_join(thread[0], &retval)); + ASSERT_EQ(NULL, retval); + ASSERT_EQ(0, pthread_join(thread[1], &retval)); + ASSERT_EQ(NULL, retval); + s.sem_destroy(); + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); + ASSERT_EQ(0, ceph_ll_unlink(cmount, root, c_file, perms)); + CLEANUP_CEPH(); +} + +/* Locking in different processes */ + +#define PROCESS_SLOW_MS() \ + static const long waitMs = 100; \ + (void) waitMs + +// Used by ConcurrentLocking test +static void process_ConcurrentRecordLocking(str_ConcurrentRecordLocking& s) { + const pid_t mypid = getpid(); + PROCESS_SLOW_MS(); + + struct ceph_mount_info *cmount = NULL; + struct timespec ts; + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + int rc; + struct flock lock1; + + STARTUP_CEPH(); + s.cmount = cmount; + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + rc = ceph_ll_create(cmount, root, s.file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, ceph_mount_perms(cmount)); + ASSERT_EQ(rc, 0); + + WAIT_MAIN(1); // (R1) + + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + PING_MAIN(1); // (1) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + PING_MAIN(2); // (2) + + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + PING_MAIN(3); // (3) + + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + PING_MAIN(4); // (4) + + WAIT_MAIN(2); // (R2) + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + PING_MAIN(5); // (5) + + WAIT_MAIN(3); // (R3) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + PING_MAIN(6); // (6) + + WAIT_MAIN(4); // (R4) + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + PING_MAIN(7); // (7) + + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); + CLEANUP_CEPH(); + + s.sem_destroy(); + exit(EXIT_SUCCESS); +} + +// Disabled because of fork() issues (http://tracker.ceph.com/issues/16556) +#ifndef _WIN32 +TEST(LibCephFS, DISABLED_InterProcessRecordLocking) { + PROCESS_SLOW_MS(); + // Process synchronization + char c_file[1024]; + const pid_t mypid = getpid(); + sprintf(c_file, "recordlock_test_%d", mypid); + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + struct flock lock1; + int rc; + + // Note: the semaphores MUST be on a shared memory segment + str_ConcurrentRecordLocking *const shs = + reinterpret_cast<str_ConcurrentRecordLocking*> + (mmap(0, sizeof(*shs), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, + -1, 0)); + str_ConcurrentRecordLocking &s = *shs; + s.file = c_file; + s.sem_init(1); + + // Start locker process + const pid_t pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + process_ConcurrentRecordLocking(s); + exit(EXIT_FAILURE); + } + + struct timespec ts; + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + UserPerm *perms = ceph_mount_perms(cmount); + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + rc = ceph_ll_create(cmount, root, c_file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, perms); + ASSERT_EQ(rc, 0); + + // Lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + + // Synchronization point with process (failure: process is dead) + PING_WORKER(1); // (R1) + WAIT_WORKER(1); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Shall have lock + // Synchronization point with process (failure: process is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with process (failure: process is dead) + WAIT_WORKER(3); // (3) + + // Wait for process to share lock + WAIT_WORKER(4); // (4) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Wake up process to unlock shared lock + PING_WORKER(2); // (R2) + WAIT_WORKER(5); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + + // Wake up process to lock shared lock + PING_WORKER(3); // (R3) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6); // (6) + + // Release lock ; process will get it + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + WAIT_WORKER(6); // (6) + + // We no longer have the lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Wake up process to unlock exclusive lock + PING_WORKER(4); // (R4) + WAIT_WORKER(7); // (7) + + // We can lock it again + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Wait pid + int status; + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_EQ(EXIT_SUCCESS, status); + + // Cleanup + s.sem_destroy(); + ASSERT_EQ(0, munmap(shs, sizeof(*shs))); + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); + ASSERT_EQ(0, ceph_ll_unlink(cmount, root, c_file, perms)); + CLEANUP_CEPH(); +} +#endif + +#ifndef _WIN32 +// Disabled because of fork() issues (http://tracker.ceph.com/issues/16556) +TEST(LibCephFS, DISABLED_ThreesomeInterProcessRecordLocking) { + PROCESS_SLOW_MS(); + // Process synchronization + char c_file[1024]; + const pid_t mypid = getpid(); + sprintf(c_file, "recordlock_test_%d", mypid); + Fh *fh = NULL; + Inode *root = NULL, *inode = NULL; + struct ceph_statx stx; + struct flock lock1; + int rc; + + // Note: the semaphores MUST be on a shared memory segment + str_ConcurrentRecordLocking *const shs = + reinterpret_cast<str_ConcurrentRecordLocking*> + (mmap(0, sizeof(*shs), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, + -1, 0)); + str_ConcurrentRecordLocking &s = *shs; + s.file = c_file; + s.sem_init(1); + + // Start locker processes + pid_t pid[2]; + pid[0] = fork(); + ASSERT_GE(pid[0], 0); + if (pid[0] == 0) { + process_ConcurrentRecordLocking(s); + exit(EXIT_FAILURE); + } + pid[1] = fork(); + ASSERT_GE(pid[1], 0); + if (pid[1] == 0) { + process_ConcurrentRecordLocking(s); + exit(EXIT_FAILURE); + } + + struct timespec ts; + struct ceph_mount_info *cmount; + STARTUP_CEPH(); + + // Get the root inode + rc = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(rc, 0); + + // Get the inode and Fh corresponding to c_file + UserPerm *perms = ceph_mount_perms(cmount); + rc = ceph_ll_create(cmount, root, c_file, fileMode, O_RDWR | O_CREAT, + &inode, &fh, &stx, 0, 0, perms); + ASSERT_EQ(rc, 0); + + // Lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + + // Synchronization point with process (failure: process is dead) + TWICE(PING_WORKER(1)); // (R1) + TWICE(WAIT_WORKER(1)); // (1) + + // Shall not have lock immediately + NOT_WAIT_WORKER(2); // (2) + + // Unlock + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Shall have lock + TWICE(// Synchronization point with process (failure: process is dead) + WAIT_WORKER(2); // (2) + + // Synchronization point with process (failure: process is dead) + WAIT_WORKER(3)); // (3) + + // Wait for process to share lock + TWICE(WAIT_WORKER(4)); // (4) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Wake up process to unlock shared lock + TWICE(PING_WORKER(2); // (R2) + WAIT_WORKER(5)); // (5) + + // Now we can lock exclusively + // Upgrade to exclusive lock (as per POSIX) + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, true)); + + TWICE( // Wake up process to lock shared lock + PING_WORKER(3); // (R3) + + // Shall not have lock immediately + NOT_WAIT_WORKER(6)); // (6) + + // Release lock ; process will get it + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + TWICE(WAIT_WORKER(6); // (6) + + // We no longer have the lock + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + lock1.l_type = F_RDLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(-CEPHFS_EAGAIN, ceph_ll_setlk(cmount, fh, &lock1, ceph_pthread_self(), false)); + + // Wake up process to unlock exclusive lock + PING_WORKER(4); // (R4) + WAIT_WORKER(7); // (7) + ); + + // We can lock it again + lock1.l_type = F_WRLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + lock1.l_type = F_UNLCK; + lock1.l_whence = SEEK_SET; + lock1.l_start = 0; + lock1.l_len = 1024; + lock1.l_pid = getpid(); + ASSERT_EQ(0, ceph_ll_setlk(cmount, fh, &lock1, mypid, false)); + + // Wait pids + int status; + ASSERT_EQ(pid[0], waitpid(pid[0], &status, 0)); + ASSERT_EQ(EXIT_SUCCESS, status); + ASSERT_EQ(pid[1], waitpid(pid[1], &status, 0)); + ASSERT_EQ(EXIT_SUCCESS, status); + + // Cleanup + s.sem_destroy(); + ASSERT_EQ(0, munmap(shs, sizeof(*shs))); + ASSERT_EQ(0, ceph_ll_close(cmount, fh)); + ASSERT_EQ(0, ceph_ll_unlink(cmount, root, c_file, perms)); + CLEANUP_CEPH(); +} +#endif diff --git a/src/test/libcephfs/snapdiff.cc b/src/test/libcephfs/snapdiff.cc new file mode 100644 index 000000000..2320bf58b --- /dev/null +++ b/src/test/libcephfs/snapdiff.cc @@ -0,0 +1,1684 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "include/cephfs/libcephfs.h" +#include "include/stat.h" +#include "include/ceph_assert.h" +#include "include/object.h" +#include "include/stringify.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string> +#include <vector> +#include <algorithm> +#include <limits.h> +#include <dirent.h> + +using namespace std; +class TestMount { + ceph_mount_info* cmount = nullptr; + char dir_path[64]; + +public: + TestMount( const char* root_dir_name = "dir0") { + ceph_create(&cmount, NULL); + ceph_conf_read_file(cmount, NULL); + ceph_conf_parse_env(cmount, NULL); + ceph_assert(0 == ceph_mount(cmount, NULL)); + + sprintf(dir_path, "/%s_%d", root_dir_name, getpid()); + ceph_assert(0 == ceph_mkdir(cmount, dir_path, 0777)); + } + ~TestMount() + { + if (cmount) { + ceph_assert(0 == purge_dir("")); + } + ceph_rmdir(cmount, dir_path); + ceph_shutdown(cmount); + } + + int conf_get(const char *option, char *buf, size_t len) { + return ceph_conf_get(cmount, option, buf, len); + } + + string make_file_path(const char* relpath) { + char path[PATH_MAX]; + sprintf(path, "%s/%s", dir_path, relpath); + return path; + } + + string make_snap_name(const char* name) { + char snap_name[64]; + if (name && *name) { + sprintf(snap_name, "%s_%d", name, getpid()); + } else { + // just simulate empty snapname + snap_name[0] = 0; + } + return snap_name; + } + string make_snap_path(const char* sname, const char* subdir = nullptr) { + char snap_path[PATH_MAX]; + string snap_name = subdir ? + concat_path(make_snap_name(sname), subdir) : + make_snap_name(sname); + sprintf(snap_path, ".snap/%s", snap_name.c_str()); + return snap_path; + } + + int mksnap(const char* name) { + string snap_name = make_snap_name(name); + return ceph_mksnap(cmount, dir_path, snap_name.c_str(), + 0755, nullptr, 0); + } + int rmsnap(const char* name) { + string snap_name = make_snap_name(name); + return ceph_rmsnap(cmount, dir_path, snap_name.c_str()); + } + int get_snapid(const char* name, uint64_t* res) + { + ceph_assert(res); + snap_info snap_info; + + char snap_path[PATH_MAX]; + string snap_name = make_snap_name(name); + sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name.c_str()); + int r = ceph_get_snap_info(cmount, snap_path, &snap_info); + if (r >= 0) { + *res = snap_info.id; + r = 0; + } + return r; + } + + int write_full(const char* relpath, const string& data) + { + auto file_path = make_file_path(relpath); + int fd = ceph_open(cmount, file_path.c_str(), O_WRONLY | O_CREAT, 0666); + if (fd < 0) { + return -EACCES; + } + int r = ceph_write(cmount, fd, data.c_str(), data.size(), 0); + if (r >= 0) { + ceph_truncate(cmount, file_path.c_str(), data.size()); + ceph_fsync(cmount, fd, 0); + } + ceph_close(cmount, fd); + return r; + } + string concat_path(string_view path, string_view name) { + string s(path); + if (s.empty() || s.back() != '/') { + s += '/'; + } + s += name; + return s; + } + int unlink(const char* relpath) + { + auto file_path = make_file_path(relpath); + return ceph_unlink(cmount, file_path.c_str()); + } + + int test_open(const char* relpath) + { + auto subdir_path = make_file_path(relpath); + struct ceph_dir_result* ls_dir; + int r = ceph_opendir(cmount, subdir_path.c_str(), &ls_dir); + if (r != 0) { + return r; + } + ceph_assert(0 == ceph_closedir(cmount, ls_dir)); + return r; + } + + int for_each_readdir(const char* relpath, + std::function<bool(const dirent*, const struct ceph_statx*)> fn) + { + auto subdir_path = make_file_path(relpath); + struct ceph_dir_result* ls_dir; + int r = ceph_opendir(cmount, subdir_path.c_str(), &ls_dir); + if (r != 0) { + return r; + } + + while (1) { + struct dirent result; + struct ceph_statx stx; + + r = ceph_readdirplus_r( + cmount, ls_dir, &result, &stx, CEPH_STATX_BASIC_STATS, + 0, + NULL); + if (!r) + break; + if (r < 0) { + std::cerr << "ceph_readdirplus_r failed, error: " + << r << std::endl; + return r; + } + + if (strcmp(result.d_name, ".") == 0 || + strcmp(result.d_name, "..") == 0) { + continue; + } + if (!fn(&result, &stx)) { + r = -EINTR; + break; + } + } + ceph_assert(0 == ceph_closedir(cmount, ls_dir)); + return r; + } + int readdir_and_compare(const char* relpath, + const vector<string>& expected0) + { + vector<string> expected(expected0); + auto end = expected.end(); + int r = for_each_readdir(relpath, + [&](const dirent* dire, const struct ceph_statx* stx) { + + std::string name(dire->d_name); + auto it = std::find(expected.begin(), end, name); + if (it == end) { + std::cerr << "readdir_and_compare error: unexpected name:" + << name << std::endl; + return false; + } + expected.erase(it); + return true; + }); + if (r == 0 && !expected.empty()) { + std::cerr << __func__ << " error: left entries:" << std::endl; + for (auto& e : expected) { + std::cerr << e << std::endl; + } + std::cerr << __func__ << " ************" << std::endl; + r = -ENOTEMPTY; + } + return r; + } + int for_each_readdir_snapdiff(const char* relpath, + const char* snap1, + const char* snap2, + std::function<bool(const dirent*, uint64_t)> fn) + { + auto s1 = make_snap_name(snap1); + auto s2 = make_snap_name(snap2); + ceph_snapdiff_info info; + ceph_snapdiff_entry_t res_entry; + int r = ceph_open_snapdiff(cmount, + dir_path, + relpath, + s1.c_str(), + s2.c_str(), + &info); + if (r != 0) { + std::cerr << " Failed to open snapdiff, ret:" << r << std::endl; + return r; + } + while (0 < (r = ceph_readdir_snapdiff(&info, + &res_entry))) { + if (strcmp(res_entry.dir_entry.d_name, ".") == 0 || + strcmp(res_entry.dir_entry.d_name, "..") == 0) { + continue; + } + if (!fn(&res_entry.dir_entry, res_entry.snapid)) { + r = -EINTR; + break; + } + } + ceph_assert(0 == ceph_close_snapdiff(&info)); + if (r != 0) { + std::cerr << " Failed to readdir snapdiff, ret:" << r + << " " << relpath << ", " << snap1 << " vs. " << snap2 + << std::endl; + } + return r; + } + int readdir_snapdiff_and_compare(const char* relpath, + const char* snap1, + const char* snap2, + const vector<pair<string, uint64_t>>& expected0) + { + vector<pair<string, uint64_t>> expected(expected0); + auto end = expected.end(); + int r = for_each_readdir_snapdiff(relpath, snap1, snap2, + [&](const dirent* dire, uint64_t snapid) { + + pair<string, uint64_t> p = std::make_pair(dire->d_name, snapid); + auto it = std::find(expected.begin(), end, p); + if (it == end) { + std::cerr << "readdir_snapdiff_and_compare error: unexpected name:" + << dire->d_name << "/" << snapid << std::endl; + return false; + } + expected.erase(it); + return true; + }); + if (r == 0 && !expected.empty()) { + std::cerr << __func__ << " error: left entries:" << std::endl; + for (auto& e : expected) { + std::cerr << e.first << "/" << e.second << std::endl; + } + std::cerr << __func__ << " ************" << std::endl; + r = -ENOTEMPTY; + } + return r; + } + + int mkdir(const char* relpath) + { + auto path = make_file_path(relpath); + return ceph_mkdir(cmount, path.c_str(), 0777); + } + int rmdir(const char* relpath) + { + auto path = make_file_path(relpath); + return ceph_rmdir(cmount, path.c_str()); + } + int purge_dir(const char* relpath0, bool inclusive = true) + { + int r = + for_each_readdir(relpath0, + [&](const dirent* dire, const struct ceph_statx* stx) { + string relpath = concat_path(relpath0, dire->d_name); + if (S_ISDIR(stx->stx_mode)) { + purge_dir(relpath.c_str()); + rmdir(relpath.c_str()); + } else { + unlink(relpath.c_str()); + } + return true; + }); + if (r != 0) { + return r; + } + if (*relpath0 != 0) { + r = rmdir(relpath0); + } + return r; + } + + void remove_all() { + purge_dir("/", false); + } + + ceph_mount_info* get_cmount() { + return cmount; + } + + void verify_snap_diff(vector<pair<string, uint64_t>>& expected, + const char* relpath, + const char* snap1, + const char* snap2); + void print_snap_diff(const char* relpath, + const char* snap1, + const char* snap2); + + void prepareSnapDiffLib1Cases(); + void prepareSnapDiffLib2Cases(); + void prepareSnapDiffLib3Cases(); + void prepareHugeSnapDiff(const std::string& name_prefix_start, + const std::string& name_prefix_bulk, + const std::string& name_prefix_end, + size_t file_count, + bool bulk_diff); +}; + +// Helper function to verify readdir_snapdiff returns expected results +void TestMount::verify_snap_diff(vector<pair<string, uint64_t>>& expected, + const char* relpath, + const char* snap1, + const char* snap2) +{ + std::cout << "---------" << snap1 << " vs. " << snap2 + << " diff listing verification for /" << (relpath ? relpath : "") + << std::endl; + ASSERT_EQ(0, + readdir_snapdiff_and_compare(relpath, snap1, snap2, expected)); +}; + +// Helper function to print readdir_snapdiff results +void TestMount::print_snap_diff(const char* relpath, + const char* snap1, + const char* snap2) +{ + std::cout << "---------" << snap1 << " vs. " << snap2 + << " diff listing for /" << (relpath ? relpath : "") + << std::endl; + ASSERT_EQ(0, for_each_readdir_snapdiff(relpath, snap1, snap2, + [&](const dirent* dire, uint64_t snapid) { + std::cout << dire->d_name << " snap " << snapid << std::endl; + return true; + })); +}; + + +/* The following method creates some files/folders/snapshots layout, + described in the sheet below. + We're to test SnapDiff readdir API against that structure. + +* where: + - xN denotes file 'x' version N. + - X denotes folder name + - * denotes no/removed file/folder + +# snap1 snap2 +# fileA1 | fileA2 | +# * | fileB2 | +# fileC1 | * | +# fileD1 | fileD1 | +# dirA | dirA | +# dirA/fileA1 | dirA/fileA2 | +# * | dirB | +# * | dirB/fileb2 | +# dirC | * | +# dirC/filec1 | * | +# dirD | dirD | +# dirD/fileD1 | dirD/fileD1 | +*/ +void TestMount::prepareSnapDiffLib1Cases() +{ + //************ snap1 ************* + ASSERT_LE(0, write_full("fileA", "hello world")); + ASSERT_LE(0, write_full("fileC", "hello world to be removed")); + ASSERT_LE(0, write_full("fileD", "hello world unmodified")); + ASSERT_EQ(0, mkdir("dirA")); + ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v1")); + ASSERT_EQ(0, mkdir("dirC")); + ASSERT_LE(0, write_full("dirC/filec", "file 'C/c' v1")); + ASSERT_EQ(0, mkdir("dirD")); + ASSERT_LE(0, write_full("dirD/filed", "file 'D/d' v1")); + + ASSERT_EQ(0, mksnap("snap1")); + + //************ snap2 ************* + ASSERT_LE(0, write_full("fileA", "hello world again in A")); + ASSERT_LE(0, write_full("fileB", "hello world in B")); + ASSERT_EQ(0, unlink("fileC")); + + ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v2")); + ASSERT_EQ(0, purge_dir("dirC")); + ASSERT_EQ(0, mkdir("dirB")); + ASSERT_LE(0, write_full("dirB/fileb", "file 'B/b' v2")); + + ASSERT_EQ(0, mksnap("snap2")); +} + +/* +* Basic functionality testing for the SnapDiff readdir API +*/ +TEST(LibCephFS, SnapDiffLib) +{ + TestMount test_mount; + + // Create simple directory tree with a couple of snapshots + // to test against + test_mount.prepareSnapDiffLib1Cases(); + + uint64_t snapid1; + uint64_t snapid2; + + // learn snapshot ids and do basic verification + ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1)); + ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2)); + ASSERT_GT(snapid1, 0); + ASSERT_GT(snapid2, 0); + ASSERT_GT(snapid2, snapid1); + std::cout << snapid1 << " vs. " << snapid2 << std::endl; + + // + // Make sure root listing for snapshot snap1 is as expected + // + { + std::cout << "---------snap1 listing verification---------" << std::endl; + string snap_path = test_mount.make_snap_path("snap1"); + vector<string> expected; + expected.push_back("fileA"); + expected.push_back("fileC"); + expected.push_back("fileD"); + expected.push_back("dirA"); + expected.push_back("dirC"); + expected.push_back("dirD"); + ASSERT_EQ(0, + test_mount.readdir_and_compare(snap_path.c_str(), expected)); + } + + // + // Make sure root listing for snapshot snap2 is as expected + // + { + std::cout << "---------snap2 listing verification---------" << std::endl; + string snap_path = test_mount.make_snap_path("snap2"); + vector<string> expected; + expected.push_back("fileA"); + expected.push_back("fileB"); + expected.push_back("fileD"); + expected.push_back("dirA"); + expected.push_back("dirB"); + expected.push_back("dirD"); + ASSERT_EQ(0, + test_mount.readdir_and_compare(snap_path.c_str(), expected)); + } + + // + // Print snap1 vs. snap2 delta for the root + // + test_mount.print_snap_diff("", "snap1", "snap2"); + + // + // Make sure snap1 vs. snap2 delta for the root is as expected + // + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back("fileA", snapid2); + expected.emplace_back("fileB", snapid2); + expected.emplace_back("fileC", snapid1); + expected.emplace_back("dirA", snapid2); + expected.emplace_back("dirB", snapid2); + expected.emplace_back("dirC", snapid1); + expected.emplace_back("dirD", snapid2); + test_mount.verify_snap_diff(expected, "", "snap1", "snap2"); + } + + // + // Make sure snap1 vs. snap2 delta for /dirA is as expected + // + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back("fileA", snapid2); + test_mount.verify_snap_diff(expected, "dirA", "snap1", "snap2"); + } + + // + // Make sure snap1 vs. snap2 delta for /dirB is as expected + // + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back("fileb", snapid2); + test_mount.verify_snap_diff(expected, "dirB", "snap1", "snap2"); + } + + // + // Make sure snap1 vs. snap2 delta for /dirC is as expected + // + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back("filec", snapid1); + test_mount.verify_snap_diff(expected, "dirC", "snap2", "snap1"); + } + + // + // Make sure snap1 vs. snap2 delta for /dirD is as expected + // + { + vector<pair<string, uint64_t>> expected; + test_mount.verify_snap_diff(expected, "dirD", "snap1", "snap2"); + } + + // Make sure SnapDiff returns an error when provided with the same + // snapshot name for both parties A and B. + { + string snap_path = test_mount.make_snap_path("snap2"); + string snap_other_path = snap_path; + std::cout << "---------invalid snapdiff params, the same snaps---------" << std::endl; + ASSERT_EQ(-EINVAL, test_mount.for_each_readdir_snapdiff( + "", + "snap2", + "snap2", + [&](const dirent* dire, uint64_t snapid) { + return true; + })); + } + // Make sure SnapDiff returns an error when provided with an empty + // snapshot name for one of the parties + { + std::cout << "---------invalid snapdiff params, no snap_other ---------" << std::endl; + string snap_path = test_mount.make_snap_path("snap2"); + string snap_other_path; + ASSERT_EQ(-EINVAL, test_mount.for_each_readdir_snapdiff( + "", + "snap2", + "", + [&](const dirent* dire, uint64_t snapid) { + return true; + })); + } + + std::cout << "------------- closing -------------" << std::endl; + ASSERT_EQ(0, test_mount.purge_dir("")); + ASSERT_EQ(0, test_mount.rmsnap("snap1")); + ASSERT_EQ(0, test_mount.rmsnap("snap2")); +} + +/* The following method creates some files/folders/snapshots layout, + described in the sheet below. + We're to test SnapDiff readdir API against that structure. + +* where: + - xN denotes file 'x' version N. + - X denotes folder name + - * denotes no/removed file/folder + +# snap1 snap2 snap3 head +# fileA1 | fileA2 | fileA2 +# * | fileB2 | fileB2 +# fileC1 | * | fileC3 +# fileD1 | fileD1 | fileD3 +# * | * | fileE3 +# fileF1 | * | * +# fileG1 | fileG2 | * +# dirA | dirA | * +# dirA/fileA1 | dirA/fileA2 | * +# * | dirB | * +# * | dirB/fileb2 | * +# dirC | * | * +# dirC/filec1 | * | * +# dirD | dirD | dirD +# dirD/filed1 | dirD/filed1 | dirD/filed1 +*/ +void TestMount::prepareSnapDiffLib2Cases() +{ + //************ snap1 ************* + ASSERT_LE(0, write_full("fileA", "hello world")); + ASSERT_LE(0, write_full("fileC", "hello world to be removed temporarily")); + ASSERT_LE(0, write_full("fileD", "hello world unmodified")); + ASSERT_LE(0, write_full("fileF", "hello world to be removed completely")); + ASSERT_LE(0, write_full("fileG", "hello world to be overwritten at snap2")); + ASSERT_EQ(0, mkdir("dirA")); + ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v1")); + ASSERT_EQ(0, mkdir("dirC")); + ASSERT_LE(0, write_full("dirC/filec", "file 'C/c' v1")); + ASSERT_EQ(0, mkdir("dirD")); + ASSERT_LE(0, write_full("dirD/filed", "file 'D/d' v1")); + + ASSERT_EQ(0, mksnap("snap1")); + + //************ snap2 ************* + ASSERT_LE(0, write_full("fileA", "hello world again in A")); + ASSERT_LE(0, write_full("fileB", "hello world in B")); + ASSERT_LE(0, write_full("fileG", "hello world to be removed at snap3")); + ASSERT_EQ(0, unlink("fileC")); + ASSERT_EQ(0, unlink("fileF")); + + ASSERT_LE(0, write_full("dirA/fileA", "file 'A/a' v2")); + ASSERT_EQ(0, mkdir("dirB")); + ASSERT_LE(0, write_full("dirB/fileb", "file 'B/b' v2")); + ASSERT_EQ(0, purge_dir("dirC")); + + ASSERT_EQ(0, mksnap("snap2")); + + //************ snap3 ************* + ASSERT_LE(0, write_full("fileC", "hello world in C recovered")); + ASSERT_LE(0, write_full("fileD", "hello world in D now modified")); + ASSERT_LE(0, write_full("fileE", "file 'E' created at snap3")); + ASSERT_EQ(0, unlink("fileG")); + ASSERT_EQ(0, purge_dir("dirA")); + ASSERT_EQ(0, purge_dir("dirB")); + ASSERT_EQ(0, mksnap("snap3")); +} + +/* The following method creates a folder with tons of file + updated between two snapshots + We're to test SnapDiff readdir API against that structure. + +* where: + - xN denotes file 'x' version N. + - X denotes folder name + - * denotes no/removed file/folder + +# snap1 snap2 +* aaaaA1 | aaaaA1 | +* aaaaB1 | * | +* * | aaaaC2 | +* aaaaD1 | aaaaD2 | +# file<NNN>1 | file<NNN>2| +* fileZ1 | fileA1 | +* zzzzA1 | zzzzA1 | +* zzzzB1 | * | +* * | zzzzC2 | +* zzzzD1 | zzzzD2 | +*/ + +void TestMount::prepareHugeSnapDiff(const std::string& name_prefix_start, + const std::string& name_prefix_bulk, + const std::string& name_prefix_end, + size_t file_count, + bool bulk_diff) +{ + //************ snap1 ************* + std::string startA = name_prefix_start + "A"; + std::string startB = name_prefix_start + "B"; + std::string startC = name_prefix_start + "C"; + std::string startD = name_prefix_start + "D"; + std::string endA = name_prefix_end + "A"; + std::string endB = name_prefix_end + "B"; + std::string endC = name_prefix_end + "C"; + std::string endD = name_prefix_end + "D"; + + ASSERT_LE(0, write_full(startA.c_str(), "hello world")); + ASSERT_LE(0, write_full(startB.c_str(), "hello world")); + ASSERT_LE(0, write_full(startD.c_str(), "hello world")); + for(size_t i = 0; i < file_count; i++) { + auto s = name_prefix_bulk + stringify(i); + ASSERT_LE(0, write_full(s.c_str(), "hello world")); + } + ASSERT_LE(0, write_full(endA.c_str(), "hello world")); + ASSERT_LE(0, write_full(endB.c_str(), "hello world")); + ASSERT_LE(0, write_full(endD.c_str(), "hello world")); + + ASSERT_EQ(0, mksnap("snap1")); + + ASSERT_LE(0, unlink(startB.c_str())); + ASSERT_LE(0, write_full(startC.c_str(), "hello world2")); + ASSERT_LE(0, write_full(startD.c_str(), "hello world2")); + if (bulk_diff) { + for(size_t i = 0; i < file_count; i++) { + auto s = std::string(name_prefix_bulk) + stringify(i); + ASSERT_LE(0, write_full(s.c_str(), "hello world2")); + } + } + ASSERT_LE(0, unlink(endB.c_str())); + ASSERT_LE(0, write_full(endC.c_str(), "hello world2")); + ASSERT_LE(0, write_full(endD.c_str(), "hello world2")); + ASSERT_EQ(0, mksnap("snap2")); +} + +/* +* More versatile SnapDiff readdir API verification, +* includes 3 different snapshots and interleaving/repetitive calls to make sure +* the results aren't spoiled due to caching. +*/ +TEST(LibCephFS, SnapDiffLib2) +{ + TestMount test_mount; + + test_mount.prepareSnapDiffLib2Cases(); + + // Create simple directory tree with a couple of snapshots to test against + uint64_t snapid1; + uint64_t snapid2; + uint64_t snapid3; + ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1)); + ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2)); + ASSERT_EQ(0, test_mount.get_snapid("snap3", &snapid3)); + std::cout << snapid1 << " vs. " << snapid2 << " vs. " << snapid3 << std::endl; + ASSERT_GT(snapid1, 0); + ASSERT_GT(snapid2, 0); + ASSERT_GT(snapid3, 0); + ASSERT_GT(snapid2, snapid1); + ASSERT_GT(snapid3, snapid2); + + // define a labda which verifies snap1/snap2/snap3 listings + auto verify_snap_listing = [&]() + { + { + string snap_path = test_mount.make_snap_path("snap1"); + + std::cout << "---------snap1 listing verification---------" << std::endl; + vector<string> expected; + expected.push_back("fileA"); + expected.push_back("fileC"); + expected.push_back("fileD"); + expected.push_back("fileF"); + expected.push_back("fileG"); + expected.push_back("dirA"); + expected.push_back("dirC"); + expected.push_back("dirD"); + ASSERT_EQ(0, + test_mount.readdir_and_compare(snap_path.c_str(), expected)); + } + { + std::cout << "---------snap2 listing verification---------" << std::endl; + string snap_path = test_mount.make_snap_path("snap2"); + vector<string> expected; + expected.push_back("fileA"); + expected.push_back("fileB"); + expected.push_back("fileD"); + expected.push_back("fileG"); + expected.push_back("dirA"); + expected.push_back("dirB"); + expected.push_back("dirD"); + ASSERT_EQ(0, + test_mount.readdir_and_compare(snap_path.c_str(), expected)); + } + { + std::cout << "---------snap3 listing verification---------" << std::endl; + string snap_path = test_mount.make_snap_path("snap3"); + vector<string> expected; + expected.push_back("fileA"); + expected.push_back("fileB"); + expected.push_back("fileC"); + expected.push_back("fileD"); + expected.push_back("fileE"); + expected.push_back("dirD"); + ASSERT_EQ(0, + test_mount.readdir_and_compare(snap_path.c_str(), expected)); + } + }; + // Prepare expected delta for snap1 vs. snap2 + vector<pair<string, uint64_t>> snap1_2_diff_expected; + snap1_2_diff_expected.emplace_back("fileA", snapid2); + snap1_2_diff_expected.emplace_back("fileB", snapid2); + snap1_2_diff_expected.emplace_back("fileC", snapid1); + snap1_2_diff_expected.emplace_back("fileF", snapid1); + snap1_2_diff_expected.emplace_back("fileG", snapid2); + snap1_2_diff_expected.emplace_back("dirA", snapid2); + snap1_2_diff_expected.emplace_back("dirB", snapid2); + snap1_2_diff_expected.emplace_back("dirC", snapid1); + snap1_2_diff_expected.emplace_back("dirD", snapid2); + + // Prepare expected delta for snap1 vs. snap3 + vector<pair<string, uint64_t>> snap1_3_diff_expected; + snap1_3_diff_expected.emplace_back("fileA", snapid3); + snap1_3_diff_expected.emplace_back("fileB", snapid3); + snap1_3_diff_expected.emplace_back("fileC", snapid3); + snap1_3_diff_expected.emplace_back("fileD", snapid3); + snap1_3_diff_expected.emplace_back("fileE", snapid3); + snap1_3_diff_expected.emplace_back("fileF", snapid1); + snap1_3_diff_expected.emplace_back("fileG", snapid1); + snap1_3_diff_expected.emplace_back("dirA", snapid1); + snap1_3_diff_expected.emplace_back("dirC", snapid1); + snap1_3_diff_expected.emplace_back("dirD", snapid3); + + // Prepare expected delta for snap2 vs. snap3 + vector<pair<string, uint64_t>> snap2_3_diff_expected; + snap2_3_diff_expected.emplace_back("fileC", snapid3); + snap2_3_diff_expected.emplace_back("fileD", snapid3); + snap2_3_diff_expected.emplace_back("fileE", snapid3); + snap2_3_diff_expected.emplace_back("fileG", snapid2); + snap2_3_diff_expected.emplace_back("dirA", snapid2); + snap2_3_diff_expected.emplace_back("dirB", snapid2); + snap2_3_diff_expected.emplace_back("dirD", snapid3); + + // Check snapshot listings on a cold cache + verify_snap_listing(); + + // Check snapshot listings on a warm cache + verify_snap_listing(); // served from cache + + // Print snap1 vs. snap2 delta against the root folder + test_mount.print_snap_diff("", "snap1", "snap2"); + + // Verify snap1 vs. snap2 delta for the root + test_mount.verify_snap_diff(snap1_2_diff_expected, "", "snap1", "snap2"); + + // Check snapshot listings on a warm cache once again + // to make sure it wasn't spoiled by SnapDiff + verify_snap_listing(); // served from cache + + // Verify snap2 vs. snap1 delta + test_mount.verify_snap_diff(snap1_2_diff_expected, "", "snap2", "snap1"); + + // Check snapshot listings on a warm cache once again + // to make sure it wasn't spoiled by SnapDiff + verify_snap_listing(); // served from cache + + // Verify snap1 vs. snap3 delta for the root + test_mount.verify_snap_diff(snap1_3_diff_expected, "", "snap1", "snap3"); + + // Verify snap2 vs. snap3 delta for the root + test_mount.verify_snap_diff(snap2_3_diff_expected, "", "snap2", "snap3"); + + // Check snapshot listings on a warm cache once again + // to make sure it wasn't spoiled by SnapDiff + verify_snap_listing(); // served from cache + + // Print snap1 vs. snap2 delta against /dirA folder + test_mount.print_snap_diff("dirA", "snap1", "snap2"); + + // Verify snap1 vs. snap2 delta for /dirA + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back("fileA", snapid2); + test_mount.verify_snap_diff(expected, "dirA", "snap1", "snap2"); + } + + // Print snap1 vs. snap2 delta against /dirB folder + test_mount.print_snap_diff("dirB", "snap1", "snap2"); + + // Verify snap1 vs. snap2 delta for /dirB + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back("fileb", snapid2); + test_mount.verify_snap_diff(expected, "dirB", "snap1", "snap2"); + } + + // Print snap1 vs. snap2 delta against /dirD folder + test_mount.print_snap_diff("dirD", "snap1", "snap2"); + + // Verify snap1 vs. snap2 delta for /dirD + { + vector<pair<string, uint64_t>> expected; + test_mount.verify_snap_diff(expected, "dirD", "snap1", "snap2"); + } + + // Check snapshot listings on a warm cache once again + // to make sure it wasn't spoiled by SnapDiff + verify_snap_listing(); // served from cache + + // Verify snap1 vs. snap2 delta for the root once again + test_mount.verify_snap_diff(snap1_2_diff_expected, "", "snap1", "snap2"); + + // Verify snap2 vs. snap3 delta for the root once again + test_mount.verify_snap_diff(snap2_3_diff_expected, "", "snap3", "snap2"); + + // Verify snap1 vs. snap3 delta for the root once again + test_mount.verify_snap_diff(snap1_3_diff_expected, "", "snap1", "snap3"); + + std::cout << "------------- closing -------------" << std::endl; + ASSERT_EQ(0, test_mount.purge_dir("")); + ASSERT_EQ(0, test_mount.rmsnap("snap1")); + ASSERT_EQ(0, test_mount.rmsnap("snap2")); + ASSERT_EQ(0, test_mount.rmsnap("snap3")); +} + +/* The following method creates some files/folders/snapshots layout, + described in the sheet below. + We're to test SnapDiff against that structure. + +* where: + - xN denotes file 'x' version N. + - X denotes folder name + - * denotes no/removed file/folder + +# snap1 snap2 snap3 head +# a1 | a1 | a3 | a4 +# b1 | b2 | b3 | b3 +# c1 | * | * | * +# * | d2 | d3 | d3 +# f1 | f2 | * | * +# ff1 | ff1 | * | * +# g1 | * | g3 | g3 +# * | * | * | h4 +# i1 | i1 | i1 | i1 +# S | S | S | S +# S/sa1 | S/sa2 | S/sa3 | S/sa3 +# * | * | * | S/sh4 +# * | T | T | T +# * | T/td2 | T/td3 | T/td3 +# C | * | * | * +# C/cc1 | * | * | * +# C/C1 | * | * | * +# C/C1/c1| * | * | * +# G | * | G | G +# G/gg1 | * | G/gg3 | G/gg3 +# * | k2 | * | * +# * | l2 | l2 | * +# * | K | * | * +# * | K/kk2 | * | * +# * | * | H | H +# * | * | H/hh3 | H/hh3 +# I | I | I | * +# I/ii1 | I/ii2 | I/ii3 | * +# I/iii1 | I/iii1 | I/iii3| * +# * | * | I/iiii3| * +# * | I/J | I/J | * +# * | I/J/i2 | I/J/i3 | * +# * | I/J/j2 | I/J/j2 | * +# * | I/J/k2 | * | * +# * | * | I/J/l3 | * +# L | L | L | L +# L/ll1 | L/ll1 | L/ll3 | L/ll3 +# L/LL | L/LL | L/LL | L/LL +# * | L/LL/ll2| L/LL/ll3| L/LL/ll4 +# * | L/LM | * | * +# * | L/LM/lm2| * | * +# * | L/LN | L/LN | * +*/ +void TestMount::prepareSnapDiffLib3Cases() +{ + //************ snap1 ************* + ASSERT_LE(0, write_full("a", "file 'a' v1")); + ASSERT_LE(0, write_full("b", "file 'b' v1")); + ASSERT_LE(0, write_full("c", "file 'c' v1")); + ASSERT_LE(0, write_full("e", "file 'e' v1")); + ASSERT_LE(0, write_full("~e", "file '~e' v1")); + ASSERT_LE(0, write_full("f", "file 'f' v1")); + ASSERT_LE(0, write_full("ff", "file 'ff' v1")); + ASSERT_LE(0, write_full("g", "file 'g' v1")); + ASSERT_LE(0, write_full("i", "file 'i' v1")); + + ASSERT_EQ(0, mkdir("S")); + ASSERT_LE(0, write_full("S/sa", "file 'S/sa' v1")); + + ASSERT_EQ(0, mkdir("C")); + ASSERT_LE(0, write_full("C/cc", "file 'C/cc' v1")); + + ASSERT_EQ(0, mkdir("C/CC")); + ASSERT_LE(0, write_full("C/CC/c", "file 'C/CC/c' v1")); + + ASSERT_EQ(0, mkdir("G")); + ASSERT_LE(0, write_full("G/gg", "file 'G/gg' v1")); + + ASSERT_EQ(0, mkdir("I")); + ASSERT_LE(0, write_full("I/ii", "file 'I/ii' v1")); + ASSERT_LE(0, write_full("I/iii", "file 'I/iii' v1")); + + ASSERT_EQ(0, mkdir("L")); + ASSERT_LE(0, write_full("L/ll", "file 'L/ll' v1")); + ASSERT_EQ(0, mkdir("L/LL")); + + ASSERT_EQ(0, mksnap("snap1")); + //************ snap2 ************* + + ASSERT_LE(0, write_full("b", "file 'b' v2")); + ASSERT_EQ(0, unlink("c")); + ASSERT_LE(0, write_full("d", "file 'd' v2")); + ASSERT_LE(0, write_full("e", "file 'e' v2")); + ASSERT_LE(0, write_full("~e", "file '~e' v2")); + ASSERT_LE(0, write_full("f", "file 'f' v2")); + ASSERT_EQ(0, unlink("g")); + + ASSERT_LE(0, write_full("S/sa", "file 'S/sa' v2")); + + ASSERT_EQ(0, mkdir("T")); + ASSERT_LE(0, write_full("T/td", "file 'T/td' v2")); + + ASSERT_EQ(0, purge_dir("C")); + ASSERT_EQ(0, purge_dir("G")); + + ASSERT_LE(0, write_full("k", "file 'k' v2")); + ASSERT_LE(0, write_full("l", "file 'l' v2")); + + ASSERT_EQ(0, mkdir("K")); + ASSERT_LE(0, write_full("K/kk", "file 'K/kk' v2")); + + ASSERT_LE(0, write_full("I/ii", "file 'I/ii' v2")); + + ASSERT_EQ(0, mkdir("I/J")); + ASSERT_LE(0, write_full("I/J/i", "file 'I/J/i' v2")); + ASSERT_LE(0, write_full("I/J/j", "file 'I/J/j' v2")); + ASSERT_LE(0, write_full("I/J/k", "file 'I/J/k' v2")); + + ASSERT_LE(0, write_full("L/LL/ll", "file 'L/LL/ll' v2")); + + ASSERT_EQ(0, mkdir("L/LM")); + ASSERT_LE(0, write_full("L/LM/lm", "file 'L/LM/lm' v2")); + + ASSERT_EQ(0, mkdir("L/LN")); + + ASSERT_EQ(0, mksnap("snap2")); + //************ snap3 ************* + + ASSERT_LE(0, write_full("a", "file 'a' v3")); + ASSERT_LE(0, write_full("b", "file 'b' v3")); + ASSERT_LE(0, write_full("d", "file 'd' v3")); + ASSERT_EQ(0, unlink("e")); + ASSERT_EQ(0, unlink("~e")); + ASSERT_EQ(0, unlink("f")); + ASSERT_EQ(0, unlink("ff")); + ASSERT_LE(0, write_full("g", "file 'g' v3")); + + ASSERT_LE(0, write_full("S/sa", "file 'S/sa' v3")); + + ASSERT_LE(0, write_full("T/td", "file 'T/td' v3")); + + ASSERT_EQ(0, mkdir("G")); + ASSERT_LE(0, write_full("G/gg", "file 'G/gg' v3")); + + ASSERT_EQ(0, unlink("k")); + + ASSERT_EQ(0, purge_dir("K")); + + ASSERT_EQ(0, mkdir("H")); + ASSERT_LE(0, write_full("H/hh", "file 'H/hh' v3")); + + ASSERT_LE(0, write_full("I/ii", "file 'I/ii' v3")); + ASSERT_LE(0, write_full("I/iii", "file 'I/iii' v3")); + ASSERT_LE(0, write_full("I/iiii", "file 'I/iiii' v3")); + + ASSERT_LE(0, write_full("I/J/i", "file 'I/J/i' v3")); + ASSERT_EQ(0, unlink("I/J/k")); + ASSERT_LE(0, write_full("I/J/l", "file 'I/J/l' v3")); + + ASSERT_LE(0, write_full("L/ll", "file 'L/ll' v3")); + + ASSERT_LE(0, write_full("L/LL/ll", "file 'L/LL/ll' v3")); + + ASSERT_EQ(0, purge_dir("L/LM")); + + ASSERT_EQ(0, mksnap("snap3")); + //************ head ************* + ASSERT_LE(0, write_full("a", "file 'a' head")); + + ASSERT_LE(0, write_full("h", "file 'h' head")); + + ASSERT_LE(0, write_full("S/sh", "file 'S/sh' head")); + + ASSERT_EQ(0, unlink("l")); + + ASSERT_EQ(0, purge_dir("I")); + + ASSERT_LE(0, write_full("L/LL/ll", "file 'L/LL/ll' head")); + + ASSERT_EQ(0, purge_dir("L/LN")); +} + +// +// This case tests SnapDiff functionality for snap1/snap2 snapshot delta +// It operates against FS layout created by prepareSnapDiffCases() method, +// see relevant table before that function for FS state overview. +// +TEST(LibCephFS, SnapDiffCases1_2) +{ + TestMount test_mount; + + // Create directory tree evolving through a bunch of snapshots + test_mount.prepareSnapDiffLib3Cases(); + + uint64_t snapid1; + uint64_t snapid2; + ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1)); + ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2)); + std::cout << snapid1 << " vs. " << snapid2 << std::endl; + ASSERT_GT(snapid1, 0); + ASSERT_GT(snapid2, 0); + ASSERT_GT(snapid2, snapid1); + + // Print snapshot delta (snap1 vs. snap2) results for root in a + // human-readable form. + test_mount.print_snap_diff("", "snap1", "snap2"); + + { + // Make sure the root delta is as expected + // One should use columns snap1 and snap2 from + // the table preceeding prepareSnapDiffCases() function + // to learn which names to expect in the delta. + // + // - file 'a' is unchanged hence not present in delta + // - file 'ff' is unchanged hence not present in delta + // - file 'i' is unchanged hence not present in delta + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("b", snapid2); // file 'b' is updated in snap2 + expected.emplace_back("c", snapid1); // file 'c' is removed in snap2 + expected.emplace_back("d", snapid2); // file 'd' is created in snap2 + expected.emplace_back("e", snapid2); // file 'e' is updated in snap2 + expected.emplace_back("~e", snapid2); // file '~e' is updated in snap2 + expected.emplace_back("f", snapid2); // file 'f' is updated in snap2 + expected.emplace_back("g", snapid1); // file 'g' is removed in snap2 + expected.emplace_back("S", snapid2); // folder 'S' is present in snap2 hence reported + expected.emplace_back("T", snapid2); // folder 'T' is created in snap2 + expected.emplace_back("C", snapid1); // folder 'C' is removed in snap2 + expected.emplace_back("G", snapid1); // folder 'G' is removed in snap2 + expected.emplace_back("k", snapid2); // file 'k' is created in snap2 + expected.emplace_back("l", snapid2); // file 'l' is created in snap2 + expected.emplace_back("K", snapid2); // folder 'K' is created in snap2 + expected.emplace_back("I", snapid2); // folder 'I' is created in snap2 + expected.emplace_back("L", snapid2); // folder 'L' is present in snap2 but got more + // subfolders + test_mount.verify_snap_diff(expected, "", "snap1", "snap2"); + } + { + + // + // Make sure snapshot delta for /S (existed at both snap1 and snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("sa", snapid2); + test_mount.verify_snap_diff(expected, "S", "snap1", "snap2"); + } + { + // + // Make sure snapshot delta for /T (created at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("td", snapid2); + test_mount.verify_snap_diff(expected, "T", "snap1", "snap2"); + } + { + // + // Make sure snapshot delta for /C (removed at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("cc", snapid1); + expected.emplace_back("CC", snapid1); + test_mount.verify_snap_diff(expected, "C", "snap2", "snap1"); + } + { + // + // Make sure snapshot delta for /C/CC (removed at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("c", snapid1); + test_mount.verify_snap_diff(expected, "C/CC", "snap2", "snap1"); + } + { + // + // Make sure snapshot delta for /I (created at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ii", snapid2); + expected.emplace_back("J", snapid2); + test_mount.verify_snap_diff(expected, "I", "snap1", "snap2"); + } + { + // + // Make sure snapshot delta for /I/J (created at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("i", snapid2); + expected.emplace_back("j", snapid2); + expected.emplace_back("k", snapid2); + test_mount.verify_snap_diff(expected, "I/J", "snap1", "snap2"); + } + { + // + // Make sure snapshot delta for /L (extended at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("LL", snapid2); + expected.emplace_back("LM", snapid2); + expected.emplace_back("LN", snapid2); + test_mount.verify_snap_diff(expected, "L", "snap1", "snap2"); + } + { + // + // Make sure snapshot delta for /L/LL (updated at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ll", snapid2); + test_mount.verify_snap_diff(expected, "L/LL", "snap1", "snap2"); + } + { + // + // Make sure snapshot delta for /L/LN (created empty at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + test_mount.verify_snap_diff(expected, "L/LN", "snap1", "snap2"); + } + + { + // Make sure snapshot delta for /L/LM (created at snap2) + // is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("lm", snapid2); + test_mount.verify_snap_diff(expected, "L/LM", "snap1", "snap2"); + } + std::cout << "-------------" << std::endl; + + test_mount.remove_all(); + test_mount.rmsnap("snap1"); + test_mount.rmsnap("snap2"); + test_mount.rmsnap("snap3"); +} + +// +// This case tests SnapDiff functionality for snap2/snap3 snapshot delta +// retrieved through .snap path-based query API. +// It operates against FS layout created by prepareSnapDiffCases() method, +// see relevant table before that function for FS state overview. +// +TEST(LibCephFS, SnapDiffCases2_3) +{ + TestMount test_mount; + + // Create directory tree evolving through a bunch of snapshots + test_mount.prepareSnapDiffLib3Cases(); + + uint64_t snapid2; + uint64_t snapid3; + ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2)); + ASSERT_EQ(0, test_mount.get_snapid("snap3", &snapid3)); + std::cout << snapid2 << " vs. " << snapid3 << std::endl; + ASSERT_GT(snapid3, 0); + ASSERT_GT(snapid3, 0); + ASSERT_GT(snapid3, snapid2); + + // Print snapshot delta (snap2 vs. snap3) results for root in a + // human-readable form. + test_mount.print_snap_diff("", "snap2", "snap3"); + + { + // Make sure the root delta is as expected + // One should use columns snap1 and snap2 from + // the table preceeding prepareSnapDiffCases() function + // to learn which names to expect in the delta. + // + // - file 'c' is removed since snap1 hence not present in delta + // - file 'l' is unchanged hence not present in delta + // - file 'i' is unchanged hence not present in delta + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("a", snapid3); // file 'a' is updated in snap3 + expected.emplace_back("b", snapid3); // file 'b' is updated in snap3 + expected.emplace_back("d", snapid3); // file 'd' is updated in snap3 + expected.emplace_back("~e", snapid2); // file '~e' is removed in snap3 + expected.emplace_back("e", snapid2); // file 'e' is removed in snap3 + expected.emplace_back("f", snapid2); // file 'f' is removed in snap3 + expected.emplace_back("ff", snapid2); // file 'ff' is removed in snap3 + expected.emplace_back("g", snapid3); // file 'g' re-appeared in snap3 + expected.emplace_back("S", snapid3); // folder 'S' is present in snap3 hence reported + expected.emplace_back("T", snapid3); // folder 'T' is present in snap3 hence reported + expected.emplace_back("G", snapid3); // folder 'G' re-appeared in snap3 hence reported + expected.emplace_back("k", snapid2); // file 'k' is removed in snap3 + expected.emplace_back("K", snapid2); // folder 'K' is removed in snap3 + expected.emplace_back("H", snapid3); // folder 'H' is created in snap3 hence reported + expected.emplace_back("I", snapid3); // folder 'I' is present in snap3 hence reported + expected.emplace_back("L", snapid3); // folder 'L' is present in snap3 hence reported + test_mount.verify_snap_diff(expected, "", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /S (children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("sa", snapid3); + test_mount.verify_snap_diff(expected, "S", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /T (children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("td", snapid3); + test_mount.verify_snap_diff(expected, "T", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /G (re-appeared) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("gg", snapid3); + test_mount.verify_snap_diff(expected, "G", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /K (removed) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("kk", snapid2); + test_mount.verify_snap_diff(expected, "K", "snap3", "snap2"); + } + { + // + // Make sure snapshot delta for /H (created) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("hh", snapid3); + test_mount.verify_snap_diff(expected, "H", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /I (children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ii", snapid3); + expected.emplace_back("iii", snapid3); + expected.emplace_back("iiii", snapid3); + expected.emplace_back("J", snapid3); + test_mount.verify_snap_diff(expected, "I", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /I/J (children updated/removed) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("i", snapid3); + expected.emplace_back("k", snapid2); + expected.emplace_back("l", snapid3); + test_mount.verify_snap_diff(expected, "I/J", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /L (children updated/removed) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ll", snapid3); + expected.emplace_back("LL", snapid3); + expected.emplace_back("LM", snapid2); + expected.emplace_back("LN", snapid3); + test_mount.verify_snap_diff(expected, "L", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /L/LL (children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ll", snapid3); + test_mount.verify_snap_diff(expected, "L/LL", "snap2", "snap3"); + } + { + // + // Make sure snapshot delta for /L/LM (removed) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("lm", snapid2); + test_mount.verify_snap_diff(expected, "L/LM", "snap3", "snap2"); + } + { + // + // Make sure snapshot delta for /L/LN (created empty) is as expected + // + vector<std::pair<string, uint64_t>> expected; + test_mount.verify_snap_diff(expected, "L/LN", "snap2", "snap3"); + } + test_mount.remove_all(); + test_mount.rmsnap("snap1"); + test_mount.rmsnap("snap2"); + test_mount.rmsnap("snap3"); +} + +// +// This case tests SnapDiff functionality for snap1/snap3 snapshot delta +// retrieved through .snap path-based query API. +// It operates against FS layout created by prepareSnapDiffCases() method, +// see relevant table before that function for FS state overview. +// +TEST(LibCephFS, SnapDiffCases1_3) +{ + TestMount test_mount; + + // Create directory tree evolving through a bunch of snapshots + test_mount.prepareSnapDiffLib3Cases(); + + uint64_t snapid1; + uint64_t snapid3; + ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1)); + ASSERT_EQ(0, test_mount.get_snapid("snap3", &snapid3)); + std::cout << snapid1 << " vs. " << snapid3 << std::endl; + ASSERT_GT(snapid3, 0); + ASSERT_GT(snapid3, 0); + ASSERT_GT(snapid3, snapid1); + + // Print snapshot delta (snap2 vs. snap3) results for root in a + // human-readable form. + test_mount.print_snap_diff("", "snap1", "snap3"); + + { + // Make sure the root delta is as expected + // One should use columns snap1 and snap3 from + // the table preceeding prepareSnapDiffCases() function + // to learn which names to expect in the delta. + // + // - file 'i' is unchanged hence not present in delta + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("a", snapid3); // file 'a' is updated in snap3 + expected.emplace_back("b", snapid3); // file 'b' is updated in snap3 + expected.emplace_back("c", snapid1); // file 'c' is removed in snap2 + expected.emplace_back("d", snapid3); // file 'd' is updated in snap3 + expected.emplace_back("~e", snapid1); // file '~e' is removed in snap3 + expected.emplace_back("e", snapid1); // file 'e' is removed in snap3 + expected.emplace_back("f", snapid1); // file 'f' is removed in snap3 + expected.emplace_back("ff", snapid1); // file 'ff' is removed in snap3 + expected.emplace_back("g", snapid3); // file 'g' removed in snap2 and + // re-appeared in snap3 + expected.emplace_back("S", snapid3); // folder 'S' is present in snap3 hence reported + expected.emplace_back("T", snapid3); // folder 'T' is present in snap3 hence reported + expected.emplace_back("C", snapid1); // folder 'C' is removed in snap2 + + // folder 'G' is removed in snap2 and re-appeared in snap3 + // hence reporting it twice under different snapid + expected.emplace_back("G", snapid1); + expected.emplace_back("G", snapid3); + + expected.emplace_back("l", snapid3); // file 'l' is created in snap2 + expected.emplace_back("H", snapid3); // folder 'H' is created in snap3 hence reported + expected.emplace_back("I", snapid3); // folder 'I' is created in snap3 hence reported + expected.emplace_back("L", snapid3); // folder 'L' is created in snap3 hence reported + test_mount.verify_snap_diff(expected, "", "snap3", "snap1"); + } + { + // + // Make sure snapshot delta for /S (children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("sa", snapid3); + test_mount.verify_snap_diff(expected, "S", "snap3", "snap1"); + } + { + // + // Make sure snapshot delta for /T (created and children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("td", snapid3); + test_mount.verify_snap_diff(expected, "T", "snap3", "snap1"); + } + { + // + // Make sure snapshot delta for /C (removed) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("cc", snapid1); + expected.emplace_back("CC", snapid1); + test_mount.verify_snap_diff(expected, "C", "snap3", "snap1"); + } + { + // + // Make sure snapshot delta for /C/CC (removed) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("c", snapid1); + test_mount.verify_snap_diff(expected, "C/CC", "snap3", "snap1"); + } + { + // + // Make sure snapshot delta for /G (removed) is as expected + // For this case (G@snap1 and G@snap3 are different entries) + // the order in which snapshot names are provided is crucial. + // Making G@snap1 vs. snap3 delta returns everything from G@snap1 + // but omits any entries from G/snap3 (since it's a different entry). + // And making G@snap3 vs. snap1 delta returns everything from G@snap3 + // but nothing from snap1, + + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("gg", snapid1); + test_mount.verify_snap_diff(expected, "G", "snap1", "snap3"); + } + { + // + // Make sure snapshot delta for /G (re-created) is as expected + // The snapshot names order is important, see above. + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("gg", snapid3); + test_mount.verify_snap_diff(expected, "G", "snap3", "snap1"); + } + { + // + // Make sure snapshot delta for /H (created) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("hh", snapid3); + test_mount.verify_snap_diff(expected, "H", "snap1", "snap3"); + } + { + // + // Make sure snapshot delta for /I (chinldren updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ii", snapid3); + expected.emplace_back("iii", snapid3); + expected.emplace_back("iiii", snapid3); + expected.emplace_back("J", snapid3); + test_mount.verify_snap_diff(expected, "I", "snap1", "snap3"); + } + { + // + // Make sure snapshot delta for /I/J (created at snap2) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("i", snapid3); + expected.emplace_back("j", snapid3); + expected.emplace_back("l", snapid3); + test_mount.verify_snap_diff(expected, "I/J", "snap1", "snap3"); + } + { + // + // Make sure snapshot delta for /L is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ll", snapid3); + expected.emplace_back("LL", snapid3); + expected.emplace_back("LN", snapid3); + test_mount.verify_snap_diff(expected, "L", "snap1", "snap3"); + } + { + // + // Make sure snapshot delta for /L/LL (children updated) is as expected + // + vector<std::pair<string, uint64_t>> expected; + expected.emplace_back("ll", snapid3); + test_mount.verify_snap_diff(expected, "L/LL", "snap1", "snap3"); + } + { + vector<std::pair<string, uint64_t>> expected; + test_mount.verify_snap_diff(expected, "L/LN", "snap1", "snap3"); + } + std::cout << "-------------" << std::endl; + + test_mount.remove_all(); + test_mount.rmsnap("snap1"); + test_mount.rmsnap("snap2"); + test_mount.rmsnap("snap3"); +} + +/* +* SnapDiff readdir API testing for huge dir +* when delta is minor. +*/ +TEST(LibCephFS, HugeSnapDiffSmallDelta) +{ + TestMount test_mount; + + long int file_count = 10000; + printf("Seeding %ld files...\n", file_count); + + // Create simple directory tree with a couple of snapshots + // to test against. + string name_prefix_start = "aaaa"; + string name_prefix_bulk = "file"; + string name_prefix_end = "zzzz"; + test_mount.prepareHugeSnapDiff(name_prefix_start, + name_prefix_bulk, + name_prefix_end, + file_count, + false); + + uint64_t snapid1; + uint64_t snapid2; + + // learn snapshot ids and do basic verification + ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1)); + ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2)); + ASSERT_GT(snapid1, 0); + ASSERT_GT(snapid2, 0); + ASSERT_GT(snapid2, snapid1); + std::cout << snapid1 << " vs. " << snapid2 << std::endl; + + // + // Make sure snap1 vs. snap2 delta for the root is as expected + // + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back(name_prefix_start + "B", snapid1); + expected.emplace_back(name_prefix_start + "C", snapid2); + expected.emplace_back(name_prefix_start + "D", snapid2); + + expected.emplace_back(name_prefix_end + "B", snapid1); + expected.emplace_back(name_prefix_end + "C", snapid2); + expected.emplace_back(name_prefix_end + "D", snapid2); + test_mount.verify_snap_diff(expected, "", "snap1", "snap2"); + } + + std::cout << "------------- closing -------------" << std::endl; + ASSERT_EQ(0, test_mount.purge_dir("")); + ASSERT_EQ(0, test_mount.rmsnap("snap1")); + ASSERT_EQ(0, test_mount.rmsnap("snap2")); +} + +/* +* SnapDiff readdir API testing for huge dir +* when delta is large +*/ +TEST(LibCephFS, HugeSnapDiffLargeDelta) +{ + TestMount test_mount; + + // Calculate amount of files required to have multiple directory fragments + // using relevant config parameters. + // file_count = mds_bal_spli_size * mds_bal_fragment_fast_factor + 100 + char buf[256]; + int r = test_mount.conf_get("mds_bal_split_size", buf, sizeof(buf)); + ASSERT_TRUE(r >= 0); + long int file_count = strtol(buf, nullptr, 10); + r = test_mount.conf_get("mds_bal_fragment_fast_factor ", buf, sizeof(buf)); + ASSERT_TRUE(r >= 0); + double factor = strtod(buf, nullptr); + file_count *= factor; + file_count += 100; + printf("Seeding %ld files...\n", file_count); + + // Create simple directory tree with a couple of snapshots + // to test against. + + string name_prefix_start = "aaaa"; + string name_prefix_bulk = "file"; + string name_prefix_end = "zzzz"; + + test_mount.prepareHugeSnapDiff(name_prefix_start, + name_prefix_bulk, + name_prefix_end, + file_count, + true); + uint64_t snapid1; + uint64_t snapid2; + + // learn snapshot ids and do basic verification + ASSERT_EQ(0, test_mount.get_snapid("snap1", &snapid1)); + ASSERT_EQ(0, test_mount.get_snapid("snap2", &snapid2)); + ASSERT_GT(snapid1, 0); + ASSERT_GT(snapid2, 0); + ASSERT_GT(snapid2, snapid1); + std::cout << snapid1 << " vs. " << snapid2 << std::endl; + + // + // Make sure snap1 vs. snap2 delta for the root is as expected + // + { + vector<pair<string, uint64_t>> expected; + expected.emplace_back(name_prefix_start + "B", snapid1); + expected.emplace_back(name_prefix_start + "C", snapid2); + expected.emplace_back(name_prefix_start + "D", snapid2); + for (size_t i = 0; i < (size_t)file_count; i++) { + expected.emplace_back(name_prefix_bulk + stringify(i), snapid2); + } + expected.emplace_back(name_prefix_end + "B", snapid1); + expected.emplace_back(name_prefix_end + "C", snapid2); + expected.emplace_back(name_prefix_end + "D", snapid2); + test_mount.verify_snap_diff(expected, "", "snap1", "snap2"); + } + + std::cout << "------------- closing -------------" << std::endl; + ASSERT_EQ(0, test_mount.purge_dir("")); + ASSERT_EQ(0, test_mount.rmsnap("snap1")); + ASSERT_EQ(0, test_mount.rmsnap("snap2")); +} diff --git a/src/test/libcephfs/suidsgid.cc b/src/test/libcephfs/suidsgid.cc new file mode 100644 index 000000000..d750613eb --- /dev/null +++ b/src/test/libcephfs/suidsgid.cc @@ -0,0 +1,331 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2023 Red Hat, Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "common/ceph_argparse.h" +#include "include/buffer.h" +#include "include/fs_types.h" +#include "include/stringify.h" +#include "include/cephfs/libcephfs.h" +#include "include/rados/librados.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> +#include <iostream> +#include <vector> +#include "json_spirit/json_spirit.h" + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +using namespace std; +struct ceph_mount_info *admin; +struct ceph_mount_info *cmount; +char filename[128]; + +void run_fallocate_test_case(int mode, int result, bool with_admin=false) +{ + struct ceph_statx stx; + int flags = FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE; + + ASSERT_EQ(0, ceph_chmod(admin, filename, mode)); + + struct ceph_mount_info *_cmount = cmount; + if (with_admin) { + _cmount = admin; + } + int fd = ceph_open(_cmount, filename, O_RDWR, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_fallocate(_cmount, fd, flags, 1024, 40960)); + ASSERT_EQ(ceph_statx(_cmount, filename, &stx, CEPH_STATX_MODE, 0), 0); + std::cout << "After ceph_fallocate, mode: 0" << oct << mode << " -> 0" + << (stx.stx_mode & 07777) << dec << std::endl; + ASSERT_EQ(stx.stx_mode & (S_ISUID|S_ISGID), result); + ceph_close(_cmount, fd); +} + +rados_t cluster; + +int do_mon_command(string s, string *key) +{ + char *outs, *outbuf; + size_t outs_len, outbuf_len; + const char *ss = s.c_str(); + int r = rados_mon_command(cluster, (const char **)&ss, 1, + 0, 0, + &outbuf, &outbuf_len, + &outs, &outs_len); + if (outbuf_len) { + string s(outbuf, outbuf_len); + std::cout << "out: " << s << std::endl; + + // parse out the key + json_spirit::mValue v, k; + json_spirit::read_or_throw(s, v); + k = v.get_array()[0].get_obj().find("key")->second; + *key = k.get_str(); + std::cout << "key: " << *key << std::endl; + free(outbuf); + } else { + return -CEPHFS_EINVAL; + } + if (outs_len) { + string s(outs, outs_len); + std::cout << "outs: " << s << std::endl; + free(outs); + } + return r; +} + +void run_write_test_case(int mode, int result, bool with_admin=false) +{ + struct ceph_statx stx; + + ASSERT_EQ(0, ceph_chmod(admin, filename, mode)); + + struct ceph_mount_info *_cmount = cmount; + if (with_admin) { + _cmount = admin; + } + int fd = ceph_open(_cmount, filename, O_RDWR, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(ceph_write(_cmount, fd, "foo", 3, 0), 3); + ASSERT_EQ(ceph_statx(_cmount, filename, &stx, CEPH_STATX_MODE, 0), 0); + std::cout << "After ceph_write, mode: 0" << oct << mode << " -> 0" + << (stx.stx_mode & 07777) << dec << std::endl; + ASSERT_EQ(stx.stx_mode & (S_ISUID|S_ISGID), result); + ceph_close(_cmount, fd); +} + +void run_truncate_test_case(int mode, int result, size_t size, bool with_admin=false) +{ + struct ceph_statx stx; + + ASSERT_EQ(0, ceph_chmod(admin, filename, mode)); + + struct ceph_mount_info *_cmount = cmount; + if (with_admin) { + _cmount = admin; + } + int fd = ceph_open(_cmount, filename, O_RDWR, 0); + ASSERT_LE(0, fd); + ASSERT_GE(ceph_ftruncate(_cmount, fd, size), 0); + ASSERT_EQ(ceph_statx(_cmount, filename, &stx, CEPH_STATX_MODE, 0), 0); + std::cout << "After ceph_truncate size " << size << " mode: 0" << oct + << mode << " -> 0" << (stx.stx_mode & 07777) << dec << std::endl; + ASSERT_EQ(stx.stx_mode & (S_ISUID|S_ISGID), result); + ceph_close(_cmount, fd); +} + +TEST(SuidsgidTest, WriteClearSetuid) { + ASSERT_EQ(0, ceph_create(&admin, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(admin, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(admin, NULL)); + ASSERT_EQ(0, ceph_mount(admin, "/")); + + sprintf(filename, "/clear_suidsgid_file_%d", getpid()); + int fd = ceph_open(admin, filename, O_CREAT|O_RDWR, 0766); + ASSERT_GE(ceph_ftruncate(admin, fd, 10000000), 0); + ceph_close(admin, fd); + + string user = "clear_suidsgid_" + stringify(rand()); + // create access key + string key; + ASSERT_EQ(0, do_mon_command( + "{\"prefix\": \"auth get-or-create\", \"entity\": \"client." + user + "\", " + "\"caps\": [\"mon\", \"allow *\", \"osd\", \"allow *\", \"mgr\", \"allow *\", " + "\"mds\", \"allow *\"], \"format\": \"json\"}", &key)); + + ASSERT_EQ(0, ceph_create(&cmount, user.c_str())); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_set(cmount, "key", key.c_str())); + ASSERT_EQ(ceph_init(cmount), 0); + UserPerm *perms = ceph_userperm_new(123, 456, 0, NULL); + ASSERT_NE(nullptr, perms); + ASSERT_EQ(0, ceph_mount_perms_set(cmount, perms)); + ceph_userperm_destroy(perms); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + // 1, Commit to a non-exec file by an unprivileged user clears suid and sgid. + run_fallocate_test_case(06666, 0); // a+rws + + // 2, Commit to a group-exec file by an unprivileged user clears suid and sgid. + run_fallocate_test_case(06676, 0); // g+x,a+rws + + // 3, Commit to a user-exec file by an unprivileged user clears suid and sgid. + run_fallocate_test_case(06766, 0); // u+x,a+rws,g-x + + // 4, Commit to a all-exec file by an unprivileged user clears suid and sgid. + run_fallocate_test_case(06777, 0); // a+rwxs + + // 5, Commit to a non-exec file by root leaves suid and sgid. + run_fallocate_test_case(06666, S_ISUID|S_ISGID, true); // a+rws + + // 6, Commit to a group-exec file by root leaves suid and sgid. + run_fallocate_test_case(06676, S_ISUID|S_ISGID, true); // g+x,a+rws + + // 7, Commit to a user-exec file by root leaves suid and sgid. + run_fallocate_test_case(06766, S_ISUID|S_ISGID, true); // u+x,a+rws,g-x + + // 8, Commit to a all-exec file by root leaves suid and sgid. + run_fallocate_test_case(06777, S_ISUID|S_ISGID, true); // a+rwxs + + // 9, Commit to a group-exec file by an unprivileged user clears sgid + run_fallocate_test_case(02676, 0); // a+rw,g+rwxs + + // 10, Commit to a all-exec file by an unprivileged user clears sgid. + run_fallocate_test_case(02777, 0); // a+rwx,g+rwxs + + // 11, Write by privileged user leaves the suid and sgid + run_write_test_case(06766, S_ISUID | S_ISGID, true); + + // 12, Write by unprivileged user clears the suid and sgid + run_write_test_case(06766, 0); + + // 13, Truncate by privileged user leaves the suid and sgid + run_truncate_test_case(06766, S_ISUID | S_ISGID, 10000, true); + + // 14, Truncate by unprivileged user clears the suid and sgid + run_truncate_test_case(06766, 0, 100); + + // clean up + ceph_shutdown(cmount); + ceph_shutdown(admin); +} + +TEST(LibCephFS, ChownClearSetuid) { + 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, "/"), 0); + + Inode *root; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + char filename[32]; + sprintf(filename, "clearsetuid%x", getpid()); + + Fh *fh; + Inode *in; + struct ceph_statx stx; + const mode_t after_mode = S_IRWXU; + const mode_t before_mode = S_IRWXU | S_ISUID | S_ISGID; + const unsigned want = CEPH_STATX_UID|CEPH_STATX_GID|CEPH_STATX_MODE; + UserPerm *usercred = ceph_mount_perms(cmount); + + ceph_ll_unlink(cmount, root, filename, usercred); + ASSERT_EQ(ceph_ll_create(cmount, root, filename, before_mode, + O_RDWR|O_CREAT|O_EXCL, &in, &fh, &stx, want, 0, + usercred), 0); + + ASSERT_EQ(stx.stx_mode & (mode_t)ALLPERMS, before_mode); + + // chown -- for this we need to be "root" + UserPerm *rootcred = ceph_userperm_new(0, 0, 0, NULL); + ASSERT_TRUE(rootcred); + stx.stx_uid++; + stx.stx_gid++; + ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_UID|CEPH_SETATTR_GID, rootcred), 0); + ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, usercred), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_MODE); + ASSERT_EQ(stx.stx_mode & (mode_t)ALLPERMS, after_mode); + + /* test chown with supplementary groups, and chown with/without exe bit */ + uid_t u = 65534; + gid_t g = 65534; + gid_t gids[] = {65533,65532}; + UserPerm *altcred = ceph_userperm_new(u, g, sizeof gids / sizeof gids[0], gids); + stx.stx_uid = u; + stx.stx_gid = g; + mode_t m = S_ISGID|S_ISUID|S_IRUSR|S_IWUSR; + stx.stx_mode = m; + ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_MODE|CEPH_SETATTR_UID|CEPH_SETATTR_GID, rootcred), 0); + ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); + ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m); + /* not dropped without exe bit */ + stx.stx_gid = gids[0]; + ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_GID, altcred), 0); + ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); + ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m); + /* now check dropped with exe bit */ + m = S_ISGID|S_ISUID|S_IRWXU; + stx.stx_mode = m; + ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_STATX_MODE, altcred), 0); + ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); + ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m); + stx.stx_gid = gids[1]; + ASSERT_EQ(ceph_ll_setattr(cmount, in, &stx, CEPH_SETATTR_GID, altcred), 0); + ASSERT_EQ(ceph_ll_getattr(cmount, in, &stx, CEPH_STATX_MODE, 0, altcred), 0); + ASSERT_EQ(stx.stx_mode&(mode_t)ALLPERMS, m&(S_IRWXU|S_IRWXG|S_IRWXO)); + ceph_userperm_destroy(altcred); + + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ceph_shutdown(cmount); +} + +static int update_root_mode() +{ + struct ceph_mount_info *admin; + int r = ceph_create(&admin, NULL); + if (r < 0) + return r; + ceph_conf_read_file(admin, NULL); + ceph_conf_parse_env(admin, NULL); + ceph_conf_set(admin, "client_permissions", "false"); + r = ceph_mount(admin, "/"); + if (r < 0) + goto out; + r = ceph_chmod(admin, "/", 0777); +out: + ceph_shutdown(admin); + return r; +} + +int main(int argc, char **argv) +{ + int r = update_root_mode(); + if (r < 0) + exit(1); + + ::testing::InitGoogleTest(&argc, argv); + + srand(getpid()); + + r = rados_create(&cluster, NULL); + if (r < 0) + exit(1); + + r = rados_conf_read_file(cluster, NULL); + if (r < 0) + exit(1); + + rados_conf_parse_env(cluster, NULL); + r = rados_connect(cluster); + if (r < 0) + exit(1); + + r = RUN_ALL_TESTS(); + + rados_shutdown(cluster); + + return r; +} diff --git a/src/test/libcephfs/test.cc b/src/test/libcephfs/test.cc new file mode 100644 index 000000000..c83ddccf9 --- /dev/null +++ b/src/test/libcephfs/test.cc @@ -0,0 +1,3775 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/compat.h" +#include "gtest/gtest.h" +#include "include/cephfs/libcephfs.h" +#include "mds/mdstypes.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> +#include <sys/time.h> + +#ifndef _WIN32 +#include <sys/resource.h> +#endif + +#include "common/Clock.h" + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +#include <fmt/format.h> +#include <map> +#include <vector> +#include <thread> +#include <regex> + +using namespace std; + +TEST(LibCephFS, OpenEmptyComponent) { + + pid_t mypid = getpid(); + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + char c_dir[1024]; + sprintf(c_dir, "/open_test_%d", mypid); + struct ceph_dir_result *dirp; + + ASSERT_EQ(0, ceph_mkdirs(cmount, c_dir, 0777)); + + ASSERT_EQ(0, ceph_opendir(cmount, c_dir, &dirp)); + + char c_path[1024]; + sprintf(c_path, "/open_test_%d//created_file_%d", mypid, mypid); + int fd = ceph_open(cmount, c_path, O_RDONLY|O_CREAT, 0666); + ASSERT_LT(0, fd); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_closedir(cmount, dirp)); + ceph_shutdown(cmount); + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + fd = ceph_open(cmount, c_path, O_RDONLY, 0666); + ASSERT_LT(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + // cleanup + ASSERT_EQ(0, ceph_unlink(cmount, c_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, c_dir)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, OpenReadTruncate) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + auto path = fmt::format("test_open_rdt_{}", getpid()); + int fd = ceph_open(cmount, path.c_str(), O_WRONLY|O_CREAT, 0666); + ASSERT_LE(0, fd); + + auto data = std::string("hello world"); + ASSERT_EQ(ceph_write(cmount, fd, data.c_str(), data.size(), 0), (int)data.size()); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, path.c_str(), O_RDONLY, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(ceph_ftruncate(cmount, fd, 0), -CEPHFS_EBADF); + ASSERT_EQ(ceph_ftruncate(cmount, fd, 1), -CEPHFS_EBADF); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, OpenReadWrite) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + char c_path[1024]; + sprintf(c_path, "test_open_rdwr_%d", getpid()); + int fd = ceph_open(cmount, c_path, O_WRONLY|O_CREAT, 0666); + ASSERT_LT(0, fd); + + const char *out_buf = "hello world"; + size_t size = strlen(out_buf); + char in_buf[100]; + ASSERT_EQ(ceph_write(cmount, fd, out_buf, size, 0), (int)size); + ASSERT_EQ(ceph_read(cmount, fd, in_buf, sizeof(in_buf), 0), -CEPHFS_EBADF); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, c_path, O_RDONLY, 0); + ASSERT_LT(0, fd); + ASSERT_EQ(ceph_write(cmount, fd, out_buf, size, 0), -CEPHFS_EBADF); + ASSERT_EQ(ceph_read(cmount, fd, in_buf, sizeof(in_buf), 0), (int)size); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, c_path, O_RDWR, 0); + ASSERT_LT(0, fd); + ASSERT_EQ(ceph_write(cmount, fd, out_buf, size, 0), (int)size); + ASSERT_EQ(ceph_read(cmount, fd, in_buf, sizeof(in_buf), 0), (int)size); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, MountNonExist) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_NE(0, ceph_mount(cmount, "/non-exist")); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, MountDouble) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(-CEPHFS_EISCONN, ceph_mount(cmount, "/")); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, MountRemount) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + + CephContext *cct = ceph_get_mount_context(cmount); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_unmount(cmount)); + + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(cct, ceph_get_mount_context(cmount)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, UnmountUnmounted) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(-CEPHFS_ENOTCONN, ceph_unmount(cmount)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, ReleaseUnmounted) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_release(cmount)); +} + +TEST(LibCephFS, ReleaseMounted) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(-CEPHFS_EISCONN, ceph_release(cmount)); + ASSERT_EQ(0, ceph_unmount(cmount)); + ASSERT_EQ(0, ceph_release(cmount)); +} + +TEST(LibCephFS, UnmountRelease) { + + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + ASSERT_EQ(0, ceph_unmount(cmount)); + ASSERT_EQ(0, ceph_release(cmount)); +} + +TEST(LibCephFS, Mount) { + 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); + ceph_shutdown(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); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, OpenLayout) { + 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); + + /* valid layout */ + char test_layout_file[256]; + sprintf(test_layout_file, "test_layout_%d_b", getpid()); + int fd = ceph_open_layout(cmount, test_layout_file, O_CREAT|O_WRONLY, 0666, (1<<20), 7, (1<<20), NULL); + ASSERT_GT(fd, 0); + char poolname[80]; + ASSERT_LT(0, ceph_get_file_pool_name(cmount, fd, poolname, sizeof(poolname))); + ASSERT_LT(0, ceph_get_file_pool_name(cmount, fd, poolname, 0)); + + /* on already-written file (CEPHFS_ENOTEMPTY) */ + ceph_write(cmount, fd, "hello world", 11, 0); + ceph_close(cmount, fd); + + char xattrk[128]; + char xattrv[128]; + sprintf(xattrk, "ceph.file.layout.stripe_unit"); + sprintf(xattrv, "65536"); + ASSERT_EQ(-CEPHFS_ENOTEMPTY, ceph_setxattr(cmount, test_layout_file, xattrk, (void *)xattrv, 5, 0)); + + /* invalid layout */ + sprintf(test_layout_file, "test_layout_%d_c", getpid()); + fd = ceph_open_layout(cmount, test_layout_file, O_CREAT, 0666, (1<<20), 1, 19, NULL); + ASSERT_EQ(fd, -CEPHFS_EINVAL); + + /* with data pool */ + sprintf(test_layout_file, "test_layout_%d_d", getpid()); + fd = ceph_open_layout(cmount, test_layout_file, O_CREAT, 0666, (1<<20), 7, (1<<20), poolname); + ASSERT_GT(fd, 0); + ceph_close(cmount, fd); + + /* with metadata pool (invalid) */ + sprintf(test_layout_file, "test_layout_%d_e", getpid()); + fd = ceph_open_layout(cmount, test_layout_file, O_CREAT, 0666, (1<<20), 7, (1<<20), "metadata"); + ASSERT_EQ(fd, -CEPHFS_EINVAL); + + /* with metadata pool (does not exist) */ + sprintf(test_layout_file, "test_layout_%d_f", getpid()); + fd = ceph_open_layout(cmount, test_layout_file, O_CREAT, 0666, (1<<20), 7, (1<<20), "asdfjasdfjasdf"); + ASSERT_EQ(fd, -CEPHFS_EINVAL); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DirLs) { + + pid_t mypid = getpid(); + + 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, "/"), 0); + + struct ceph_dir_result *ls_dir = NULL; + char foostr[256]; + sprintf(foostr, "dir_ls%d", mypid); + ASSERT_EQ(ceph_opendir(cmount, foostr, &ls_dir), -CEPHFS_ENOENT); + + ASSERT_EQ(ceph_mkdir(cmount, foostr, 0777), 0); + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, foostr, &stx, 0, 0), 0); + ASSERT_NE(S_ISDIR(stx.stx_mode), 0); + + char barstr[256]; + sprintf(barstr, "dir_ls2%d", mypid); + ASSERT_EQ(ceph_statx(cmount, barstr, &stx, 0, AT_SYMLINK_NOFOLLOW), -CEPHFS_ENOENT); + + // insert files into directory and test open + char bazstr[256]; + int i = 0, r = rand() % 4096; + if (getenv("LIBCEPHFS_RAND")) { + r = atoi(getenv("LIBCEPHFS_RAND")); + } + printf("rand: %d\n", r); + for(; i < r; ++i) { + + sprintf(bazstr, "dir_ls%d/dirf%d", mypid, i); + int fd = ceph_open(cmount, bazstr, O_CREAT|O_RDONLY, 0666); + ASSERT_GT(fd, 0); + ASSERT_EQ(ceph_close(cmount, fd), 0); + + // set file sizes for readdirplus + ceph_truncate(cmount, bazstr, i); + } + + ASSERT_EQ(ceph_opendir(cmount, foostr, &ls_dir), 0); + + // not guaranteed to get . and .. first, but its a safe assumption in this case + struct dirent *result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + + std::vector<std::string> entries; + std::map<std::string, int64_t> offset_map; + int64_t offset = ceph_telldir(cmount, ls_dir); + for(i = 0; i < r; ++i) { + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + entries.push_back(result->d_name); + offset_map[result->d_name] = offset; + offset = ceph_telldir(cmount, ls_dir); + } + + ASSERT_TRUE(ceph_readdir(cmount, ls_dir) == NULL); + offset = ceph_telldir(cmount, ls_dir); + + ASSERT_EQ(offset_map.size(), entries.size()); + for(i = 0; i < r; ++i) { + sprintf(bazstr, "dirf%d", i); + ASSERT_TRUE(offset_map.count(bazstr) == 1); + } + + // test seekdir + ceph_seekdir(cmount, ls_dir, offset); + ASSERT_TRUE(ceph_readdir(cmount, ls_dir) == NULL); + + for (auto p = offset_map.begin(); p != offset_map.end(); ++p) { + ceph_seekdir(cmount, ls_dir, p->second); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + std::string d_name(result->d_name); + ASSERT_EQ(p->first, d_name); + } + + // test rewinddir + ceph_rewinddir(cmount, ls_dir); + + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + + ceph_rewinddir(cmount, ls_dir); + + int t = ceph_telldir(cmount, ls_dir); + ASSERT_GT(t, -1); + + ASSERT_TRUE(ceph_readdir(cmount, ls_dir) != NULL); + + // test seekdir - move back to the beginning + ceph_seekdir(cmount, ls_dir, t); + + // test getdents + struct dirent *getdents_entries; + size_t getdents_entries_len = (r + 2) * sizeof(*getdents_entries); + getdents_entries = (struct dirent *)malloc(getdents_entries_len); + + int count = 0; + std::vector<std::string> found; + while (true) { + int len = ceph_getdents(cmount, ls_dir, (char *)getdents_entries, getdents_entries_len); + if (len == 0) + break; + ASSERT_GT(len, 0); + ASSERT_TRUE((len % sizeof(*getdents_entries)) == 0); + int n = len / sizeof(*getdents_entries); + int j; + if (count == 0) { + ASSERT_STREQ(getdents_entries[0].d_name, "."); + ASSERT_STREQ(getdents_entries[1].d_name, ".."); + j = 2; + } else { + j = 0; + } + count += n; + for(; j < n; ++i, ++j) { + const char *name = getdents_entries[j].d_name; + found.push_back(name); + } + } + ASSERT_EQ(found, entries); + free(getdents_entries); + + // test readdir_r + ceph_rewinddir(cmount, ls_dir); + + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + + found.clear(); + while (true) { + struct dirent rdent; + int len = ceph_readdir_r(cmount, ls_dir, &rdent); + if (len == 0) + break; + ASSERT_EQ(len, 1); + found.push_back(rdent.d_name); + } + ASSERT_EQ(found, entries); + + // test readdirplus + ceph_rewinddir(cmount, ls_dir); + + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + + found.clear(); + while (true) { + struct dirent rdent; + struct ceph_statx stx; + int len = ceph_readdirplus_r(cmount, ls_dir, &rdent, &stx, + CEPH_STATX_SIZE, AT_STATX_DONT_SYNC, NULL); + if (len == 0) + break; + ASSERT_EQ(len, 1); + const char *name = rdent.d_name; + found.push_back(name); + int size; + sscanf(name, "dirf%d", &size); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_SIZE); + ASSERT_EQ(stx.stx_size, (size_t)size); + // On Windows, dirent uses long (4B) inodes, which get trimmed + // and can't be used. + // TODO: consider defining ceph_dirent. + #ifndef _WIN32 + ASSERT_EQ(stx.stx_ino, rdent.d_ino); + #endif + //ASSERT_EQ(st.st_mode, (mode_t)0666); + } + ASSERT_EQ(found, entries); + + ASSERT_EQ(ceph_closedir(cmount, ls_dir), 0); + + // cleanup + for(i = 0; i < r; ++i) { + sprintf(bazstr, "dir_ls%d/dirf%d", mypid, i); + ASSERT_EQ(0, ceph_unlink(cmount, bazstr)); + } + ASSERT_EQ(0, ceph_rmdir(cmount, foostr)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, ManyNestedDirs) { + 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); + + const char *many_path = "a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a"; + ASSERT_EQ(ceph_mkdirs(cmount, many_path, 0755), 0); + + int i = 0; + + for(; i < 39; ++i) { + ASSERT_EQ(ceph_chdir(cmount, "a"), 0); + + struct ceph_dir_result *dirp; + ASSERT_EQ(ceph_opendir(cmount, "a", &dirp), 0); + struct dirent *dent = ceph_readdir(cmount, dirp); + ASSERT_TRUE(dent != NULL); + ASSERT_STREQ(dent->d_name, "."); + dent = ceph_readdir(cmount, dirp); + ASSERT_TRUE(dent != NULL); + ASSERT_STREQ(dent->d_name, ".."); + dent = ceph_readdir(cmount, dirp); + ASSERT_TRUE(dent != NULL); + ASSERT_STREQ(dent->d_name, "a"); + ASSERT_EQ(ceph_closedir(cmount, dirp), 0); + } + + ASSERT_STREQ(ceph_getcwd(cmount), "/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a"); + + ASSERT_EQ(ceph_chdir(cmount, "a/a/a"), 0); + + for(i = 0; i < 39; ++i) { + ASSERT_EQ(ceph_chdir(cmount, ".."), 0); + ASSERT_EQ(ceph_rmdir(cmount, "a"), 0); + } + + ASSERT_EQ(ceph_chdir(cmount, "/"), 0); + + ASSERT_EQ(ceph_rmdir(cmount, "a/a/a"), 0); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Xattrs) { + 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_xattr_file[256]; + sprintf(test_xattr_file, "test_xattr_%d", getpid()); + int fd = ceph_open(cmount, test_xattr_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + + // test removing non-existent xattr + ASSERT_EQ(-CEPHFS_ENODATA, ceph_removexattr(cmount, test_xattr_file, "user.nosuchxattr")); + + char i = 'a'; + char xattrk[128]; + char xattrv[128]; + for(; i < 'a'+26; ++i) { + sprintf(xattrk, "user.test_xattr_%c", i); + int len = sprintf(xattrv, "testxattr%c", i); + ASSERT_EQ(ceph_setxattr(cmount, test_xattr_file, xattrk, (void *) xattrv, len, XATTR_CREATE), 0); + } + + // zero size should return required buffer length + int len_needed = ceph_listxattr(cmount, test_xattr_file, NULL, 0); + ASSERT_GT(len_needed, 0); + + // buffer size smaller than needed should fail + char xattrlist[128*26]; + ASSERT_GT(sizeof(xattrlist), (size_t)len_needed); + int len = ceph_listxattr(cmount, test_xattr_file, xattrlist, len_needed - 1); + ASSERT_EQ(-CEPHFS_ERANGE, len); + + len = ceph_listxattr(cmount, test_xattr_file, xattrlist, sizeof(xattrlist)); + ASSERT_EQ(len, len_needed); + char *p = xattrlist; + char *n; + i = 'a'; + while (len > 0) { + // ceph.* xattrs should not be listed + ASSERT_NE(strncmp(p, "ceph.", 5), 0); + + sprintf(xattrk, "user.test_xattr_%c", i); + ASSERT_STREQ(p, xattrk); + + char gxattrv[128]; + std::cout << "getting attr " << p << std::endl; + int alen = ceph_getxattr(cmount, test_xattr_file, p, (void *) gxattrv, 128); + ASSERT_GT(alen, 0); + sprintf(xattrv, "testxattr%c", i); + ASSERT_TRUE(!strncmp(xattrv, gxattrv, alen)); + + n = strchr(p, '\0'); + n++; + len -= (n - p); + p = n; + ++i; + } + + i = 'a'; + for(i = 'a'; i < 'a'+26; ++i) { + sprintf(xattrk, "user.test_xattr_%c", i); + ASSERT_EQ(ceph_removexattr(cmount, test_xattr_file, xattrk), 0); + } + + ceph_close(cmount, fd); + ceph_shutdown(cmount); + +} + +TEST(LibCephFS, Xattrs_ll) { + 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_xattr_file[256]; + sprintf(test_xattr_file, "test_xattr_%d", getpid()); + int fd = ceph_open(cmount, test_xattr_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + ceph_close(cmount, fd); + + Inode *root = NULL; + Inode *existent_file_handle = NULL; + + int res = ceph_ll_lookup_root(cmount, &root); + ASSERT_EQ(res, 0); + + UserPerm *perms = ceph_mount_perms(cmount); + struct ceph_statx stx; + + res = ceph_ll_lookup(cmount, root, test_xattr_file, &existent_file_handle, + &stx, 0, 0, perms); + ASSERT_EQ(res, 0); + + const char *valid_name = "user.attrname"; + const char *value = "attrvalue"; + char value_buf[256] = { 0 }; + + res = ceph_ll_setxattr(cmount, existent_file_handle, valid_name, value, strlen(value), 0, perms); + ASSERT_EQ(res, 0); + + res = ceph_ll_getxattr(cmount, existent_file_handle, valid_name, value_buf, 256, perms); + ASSERT_EQ(res, (int)strlen(value)); + + value_buf[res] = '\0'; + ASSERT_STREQ(value_buf, value); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LstatSlashdot) { + 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); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, "/.", &stx, 0, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(ceph_statx(cmount, ".", &stx, 0, AT_SYMLINK_NOFOLLOW), 0); + + 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, 2u); + + { + char test_dir2[296]; + 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, 2u); + } + + { + char test_dir2[296]; + 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, 2u); + ASSERT_EQ(ceph_statx(cmount, test_dir1, &stx, CEPH_STATX_NLINK, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_nlink, 3u); + 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, 4u); + 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, 4u); + 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, 4u); + 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, 3u); + 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, 2u); + } + + 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, 0u); + + ceph_close(cmount, fd); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DoubleChmod) { + + 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_file[256]; + sprintf(test_file, "test_perms_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + // write some stuff + const char *bytes = "foobarbaz"; + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + + ceph_close(cmount, fd); + + // set perms to read but can't write + ASSERT_EQ(ceph_chmod(cmount, test_file, 0400), 0); + + fd = ceph_open(cmount, test_file, O_RDWR, 0); + ASSERT_EQ(fd, -CEPHFS_EACCES); + + fd = ceph_open(cmount, test_file, O_RDONLY, 0); + ASSERT_GT(fd, -1); + + char buf[100]; + int ret = ceph_read(cmount, fd, buf, 100, 0); + ASSERT_EQ(ret, (int)strlen(bytes)); + buf[ret] = '\0'; + ASSERT_STREQ(buf, bytes); + + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), -CEPHFS_EBADF); + + ceph_close(cmount, fd); + + // reset back to writeable + ASSERT_EQ(ceph_chmod(cmount, test_file, 0600), 0); + + // ensure perms are correct + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, test_file, &stx, CEPH_STATX_MODE, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(stx.stx_mode, 0100600U); + + fd = ceph_open(cmount, test_file, O_RDWR, 0); + ASSERT_GT(fd, 0); + + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + ceph_close(cmount, fd); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Fchmod) { + 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_file[256]; + sprintf(test_file, "test_perms_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + // write some stuff + const char *bytes = "foobarbaz"; + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + + // set perms to read but can't write + ASSERT_EQ(ceph_fchmod(cmount, fd, 0400), 0); + + char buf[100]; + int ret = ceph_read(cmount, fd, buf, 100, 0); + ASSERT_EQ(ret, (int)strlen(bytes)); + buf[ret] = '\0'; + ASSERT_STREQ(buf, bytes); + + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + + ceph_close(cmount, fd); + + ASSERT_EQ(ceph_open(cmount, test_file, O_RDWR, 0), -CEPHFS_EACCES); + + // reset back to writeable + ASSERT_EQ(ceph_chmod(cmount, test_file, 0600), 0); + + fd = ceph_open(cmount, test_file, O_RDWR, 0); + ASSERT_GT(fd, 0); + + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + ceph_close(cmount, fd); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Lchmod) { + 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_file[256]; + sprintf(test_file, "test_perms_lchmod_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + // write some stuff + const char *bytes = "foobarbaz"; + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + ceph_close(cmount, fd); + + // Create symlink + char test_symlink[256]; + sprintf(test_symlink, "test_lchmod_sym_%d", getpid()); + ASSERT_EQ(ceph_symlink(cmount, test_file, test_symlink), 0); + + // get symlink stat - lstat + struct ceph_statx stx_orig1; + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx_orig1, CEPH_STATX_ALL_STATS, AT_SYMLINK_NOFOLLOW), 0); + + // Change mode on symlink file + ASSERT_EQ(ceph_lchmod(cmount, test_symlink, 0400), 0); + struct ceph_statx stx_orig2; + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx_orig2, CEPH_STATX_ALL_STATS, AT_SYMLINK_NOFOLLOW), 0); + + // Compare modes + ASSERT_NE(stx_orig1.stx_mode, stx_orig2.stx_mode); + static const int permbits = S_IRWXU|S_IRWXG|S_IRWXO; + ASSERT_EQ(permbits&stx_orig1.stx_mode, 0777); + ASSERT_EQ(permbits&stx_orig2.stx_mode, 0400); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Fchown) { + 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_file[256]; + sprintf(test_file, "test_fchown_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + // set perms to readable and writeable only by owner + ASSERT_EQ(ceph_fchmod(cmount, fd, 0600), 0); + + // change ownership to nobody -- we assume nobody exists and id is always 65534 + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "0"), 0); + ASSERT_EQ(ceph_fchown(cmount, fd, 65534, 65534), 0); + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "1"), 0); + + ceph_close(cmount, fd); + + // "nobody" will be ignored on Windows + #ifndef _WIN32 + fd = ceph_open(cmount, test_file, O_RDWR, 0); + ASSERT_EQ(fd, -CEPHFS_EACCES); + #endif + + ceph_shutdown(cmount); +} + +#if defined(__linux__) && defined(O_PATH) +TEST(LibCephFS, FlagO_PATH) { + struct ceph_mount_info *cmount; + + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, NULL)); + + char test_file[PATH_MAX]; + sprintf(test_file, "test_oflag_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR|O_PATH, 0666); + ASSERT_EQ(-CEPHFS_ENOENT, fd); + + fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + // ok, the file has been created. perform real checks now + fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR|O_PATH, 0666); + ASSERT_GT(fd, 0); + + char buf[128]; + ASSERT_EQ(-CEPHFS_EBADF, ceph_read(cmount, fd, buf, sizeof(buf), 0)); + ASSERT_EQ(-CEPHFS_EBADF, ceph_write(cmount, fd, buf, sizeof(buf), 0)); + + // set perms to readable and writeable only by owner + ASSERT_EQ(-CEPHFS_EBADF, ceph_fchmod(cmount, fd, 0600)); + + // change ownership to nobody -- we assume nobody exists and id is always 65534 + ASSERT_EQ(-CEPHFS_EBADF, ceph_fchown(cmount, fd, 65534, 65534)); + + // try to sync + ASSERT_EQ(-CEPHFS_EBADF, ceph_fsync(cmount, fd, false)); + + struct ceph_statx stx; + ASSERT_EQ(0, ceph_fstatx(cmount, fd, &stx, 0, 0)); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ceph_shutdown(cmount); +} +#endif /* __linux */ + +TEST(LibCephFS, Symlinks) { + 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_file[256]; + sprintf(test_file, "test_symlinks_%d", getpid()); + + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + ceph_close(cmount, fd); + + char test_symlink[256]; + sprintf(test_symlink, "test_symlinks_sym_%d", getpid()); + + ASSERT_EQ(ceph_symlink(cmount, test_file, test_symlink), 0); + + // test the O_NOFOLLOW case + fd = ceph_open(cmount, test_symlink, O_NOFOLLOW, 0); + ASSERT_EQ(fd, -CEPHFS_ELOOP); + + // stat the original file + struct ceph_statx stx_orig; + ASSERT_EQ(ceph_statx(cmount, test_file, &stx_orig, CEPH_STATX_ALL_STATS, 0), 0); + // stat the symlink + struct ceph_statx stx_symlink_orig; + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx_symlink_orig, CEPH_STATX_ALL_STATS, 0), 0); + // ensure the statx bufs are equal + ASSERT_EQ(memcmp(&stx_orig, &stx_symlink_orig, sizeof(stx_orig)), 0); + + sprintf(test_file, "/test_symlinks_abs_%d", getpid()); + + fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + ceph_close(cmount, fd); + + sprintf(test_symlink, "/test_symlinks_abs_sym_%d", getpid()); + + ASSERT_EQ(ceph_symlink(cmount, test_file, test_symlink), 0); + + // stat the original file + ASSERT_EQ(ceph_statx(cmount, test_file, &stx_orig, CEPH_STATX_ALL_STATS, 0), 0); + // stat the symlink + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx_symlink_orig, CEPH_STATX_ALL_STATS, 0), 0); + // ensure the statx bufs are equal + ASSERT_TRUE(!memcmp(&stx_orig, &stx_symlink_orig, sizeof(stx_orig))); + + // test lstat + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx_orig, CEPH_STATX_ALL_STATS, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_TRUE(S_ISLNK(stx_orig.stx_mode)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DirSyms) { + 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); + + char test_symdir[256]; + sprintf(test_symdir, "symdir_symlinks_%d", getpid()); + + ASSERT_EQ(ceph_symlink(cmount, test_dir1, test_symdir), 0); + + char test_file[256]; + sprintf(test_file, "/symdir_symlinks_%d/test_symdir_file", getpid()); + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0600); + ASSERT_GT(fd, 0); + ceph_close(cmount, fd); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, test_file, &stx, 0, AT_SYMLINK_NOFOLLOW), 0); + + // ensure that its a file not a directory we get back + ASSERT_TRUE(S_ISREG(stx.stx_mode)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LoopSyms) { + 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_loopsym_%d", getpid()); + + ASSERT_EQ(ceph_mkdir(cmount, test_dir1, 0700), 0); + + char test_dir2[256]; + sprintf(test_dir2, "/dir1_loopsym_%d/loop_dir", getpid()); + + ASSERT_EQ(ceph_mkdir(cmount, test_dir2, 0700), 0); + + // symlink it itself: /path/to/mysym -> /path/to/mysym + char test_symdir[256]; + sprintf(test_symdir, "/dir1_loopsym_%d/loop_dir/symdir", getpid()); + + ASSERT_EQ(ceph_symlink(cmount, test_symdir, test_symdir), 0); + + char test_file[256]; + sprintf(test_file, "/dir1_loopsym_%d/loop_dir/symdir/test_loopsym_file", getpid()); + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0600); + ASSERT_EQ(fd, -CEPHFS_ELOOP); + + // loop: /a -> /b, /b -> /c, /c -> /a + char a[264], b[264], c[264]; + sprintf(a, "/%s/a", test_dir1); + sprintf(b, "/%s/b", test_dir1); + sprintf(c, "/%s/c", test_dir1); + ASSERT_EQ(ceph_symlink(cmount, a, b), 0); + ASSERT_EQ(ceph_symlink(cmount, b, c), 0); + ASSERT_EQ(ceph_symlink(cmount, c, a), 0); + ASSERT_EQ(ceph_open(cmount, a, O_RDWR, 0), -CEPHFS_ELOOP); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, HardlinkNoOriginal) { + + int mypid = getpid(); + + 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 dir[256]; + sprintf(dir, "/test_rmdirfail%d", mypid); + ASSERT_EQ(ceph_mkdir(cmount, dir, 0777), 0); + + ASSERT_EQ(ceph_chdir(cmount, dir), 0); + + int fd = ceph_open(cmount, "f1", O_CREAT, 0644); + ASSERT_GT(fd, 0); + + ceph_close(cmount, fd); + + // create hard link + ASSERT_EQ(ceph_link(cmount, "f1", "hardl1"), 0); + + // remove file link points to + ASSERT_EQ(ceph_unlink(cmount, "f1"), 0); + + ceph_shutdown(cmount); + + // now cleanup + 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); + ASSERT_EQ(ceph_chdir(cmount, dir), 0); + ASSERT_EQ(ceph_unlink(cmount, "hardl1"), 0); + ASSERT_EQ(ceph_rmdir(cmount, dir), 0); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, BadArgument) { + 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); + + int fd = ceph_open(cmount, "test_file", O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + char buf[100]; + ASSERT_EQ(ceph_write(cmount, fd, buf, sizeof(buf), 0), (int)sizeof(buf)); + ASSERT_EQ(ceph_read(cmount, fd, buf, 0, 5), 0); + ceph_close(cmount, fd); + ASSERT_EQ(ceph_unlink(cmount, "test_file"), 0); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, BadFileDesc) { + 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); + + ASSERT_EQ(ceph_fchmod(cmount, -1, 0655), -CEPHFS_EBADF); + ASSERT_EQ(ceph_close(cmount, -1), -CEPHFS_EBADF); + ASSERT_EQ(ceph_lseek(cmount, -1, 0, SEEK_SET), -CEPHFS_EBADF); + + char buf[0]; + ASSERT_EQ(ceph_read(cmount, -1, buf, 0, 0), -CEPHFS_EBADF); + ASSERT_EQ(ceph_write(cmount, -1, buf, 0, 0), -CEPHFS_EBADF); + + ASSERT_EQ(ceph_ftruncate(cmount, -1, 0), -CEPHFS_EBADF); + ASSERT_EQ(ceph_fsync(cmount, -1, 0), -CEPHFS_EBADF); + + struct ceph_statx stx; + ASSERT_EQ(ceph_fstatx(cmount, -1, &stx, 0, 0), -CEPHFS_EBADF); + + struct sockaddr_storage addr; + ASSERT_EQ(ceph_get_file_stripe_address(cmount, -1, 0, &addr, 1), -CEPHFS_EBADF); + + ASSERT_EQ(ceph_get_file_stripe_unit(cmount, -1), -CEPHFS_EBADF); + ASSERT_EQ(ceph_get_file_pool(cmount, -1), -CEPHFS_EBADF); + char poolname[80]; + ASSERT_EQ(ceph_get_file_pool_name(cmount, -1, poolname, sizeof(poolname)), -CEPHFS_EBADF); + ASSERT_EQ(ceph_get_file_replication(cmount, -1), -CEPHFS_EBADF); + ASSERT_EQ(ceph_get_file_object_size(cmount, -1), -CEPHFS_EBADF); + int stripe_unit, stripe_count, object_size, pg_pool; + ASSERT_EQ(ceph_get_file_layout(cmount, -1, &stripe_unit, &stripe_count, &object_size, &pg_pool), -CEPHFS_EBADF); + ASSERT_EQ(ceph_get_file_stripe_count(cmount, -1), -CEPHFS_EBADF); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, ReadEmptyFile) { + 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); + + // test the read_sync path in the client for zero files + ASSERT_EQ(ceph_conf_set(cmount, "client_debug_force_sync_read", "true"), 0); + + int mypid = getpid(); + char testf[256]; + + sprintf(testf, "test_reademptyfile%d", mypid); + int fd = ceph_open(cmount, testf, O_CREAT|O_TRUNC|O_WRONLY, 0644); + ASSERT_GT(fd, 0); + + ceph_close(cmount, fd); + + fd = ceph_open(cmount, testf, O_RDONLY, 0); + ASSERT_GT(fd, 0); + + char buf[4096]; + ASSERT_EQ(ceph_read(cmount, fd, buf, 4096, 0), 0); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, PreadvPwritev) { + 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); + + int mypid = getpid(); + char testf[256]; + + sprintf(testf, "test_preadvpwritevfile%d", mypid); + int fd = ceph_open(cmount, testf, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + char out0[] = "hello "; + char out1[] = "world\n"; + struct iovec iov_out[2] = { + {out0, sizeof(out0)}, + {out1, sizeof(out1)}, + }; + char in0[sizeof(out0)]; + char in1[sizeof(out1)]; + struct iovec iov_in[2] = { + {in0, sizeof(in0)}, + {in1, sizeof(in1)}, + }; + ssize_t nwritten = iov_out[0].iov_len + iov_out[1].iov_len; + ssize_t nread = iov_in[0].iov_len + iov_in[1].iov_len; + + ASSERT_EQ(ceph_pwritev(cmount, fd, iov_out, 2, 0), nwritten); + ASSERT_EQ(ceph_preadv(cmount, fd, iov_in, 2, 0), nread); + ASSERT_EQ(0, strncmp((const char*)iov_in[0].iov_base, (const char*)iov_out[0].iov_base, iov_out[0].iov_len)); + ASSERT_EQ(0, strncmp((const char*)iov_in[1].iov_base, (const char*)iov_out[1].iov_base, iov_out[1].iov_len)); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LlreadvLlwritev) { + 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); + + int mypid = getpid(); + char filename[256]; + + sprintf(filename, "test_llreadvllwritevfile%u", mypid); + + Inode *root, *file; + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + Fh *fh; + struct ceph_statx stx; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, + O_RDWR|O_CREAT|O_TRUNC, &file, &fh, &stx, 0, 0, perms), 0); + + /* Reopen read-only */ + char out0[] = "hello "; + char out1[] = "world\n"; + struct iovec iov_out[2] = { + {out0, sizeof(out0)}, + {out1, sizeof(out1)}, + }; + char in0[sizeof(out0)]; + char in1[sizeof(out1)]; + struct iovec iov_in[2] = { + {in0, sizeof(in0)}, + {in1, sizeof(in1)}, + }; + ssize_t nwritten = iov_out[0].iov_len + iov_out[1].iov_len; + ssize_t nread = iov_in[0].iov_len + iov_in[1].iov_len; + + ASSERT_EQ(ceph_ll_writev(cmount, fh, iov_out, 2, 0), nwritten); + ASSERT_EQ(ceph_ll_readv(cmount, fh, iov_in, 2, 0), nread); + ASSERT_EQ(0, strncmp((const char*)iov_in[0].iov_base, (const char*)iov_out[0].iov_base, iov_out[0].iov_len)); + ASSERT_EQ(0, strncmp((const char*)iov_in[1].iov_base, (const char*)iov_out[1].iov_base, iov_out[1].iov_len)); + + ceph_ll_close(cmount, fh); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, StripeUnitGran) { + 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); + ASSERT_GT(ceph_get_stripe_unit_granularity(cmount), 0); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Rename) { + 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); + + int mypid = getpid(); + char path_src[256]; + char path_dst[256]; + + /* make a source file */ + sprintf(path_src, "test_rename_src%d", mypid); + int fd = ceph_open(cmount, path_src, O_CREAT|O_TRUNC|O_WRONLY, 0777); + ASSERT_GT(fd, 0); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + /* rename to a new dest path */ + sprintf(path_dst, "test_rename_dst%d", mypid); + ASSERT_EQ(0, ceph_rename(cmount, path_src, path_dst)); + + /* test that dest path exists */ + struct ceph_statx stx; + ASSERT_EQ(0, ceph_statx(cmount, path_dst, &stx, 0, 0)); + + /* test that src path doesn't exist */ + ASSERT_EQ(-CEPHFS_ENOENT, ceph_statx(cmount, path_src, &stx, 0, AT_SYMLINK_NOFOLLOW)); + + /* rename with non-existent source path */ + ASSERT_EQ(-CEPHFS_ENOENT, ceph_rename(cmount, path_src, path_dst)); + + ASSERT_EQ(0, ceph_unlink(cmount, path_dst)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, UseUnmounted) { + 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)); + + struct statvfs stvfs; + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_statfs(cmount, "/", &stvfs)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_local_osd(cmount)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_chdir(cmount, "/")); + + struct ceph_dir_result *dirp; + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_opendir(cmount, "/", &dirp)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_closedir(cmount, dirp)); + + ceph_readdir(cmount, dirp); + EXPECT_EQ(CEPHFS_ENOTCONN, errno); + + struct dirent rdent; + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_readdir_r(cmount, dirp, &rdent)); + + struct ceph_statx stx; + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_readdirplus_r(cmount, dirp, &rdent, &stx, 0, 0, NULL)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_getdents(cmount, dirp, NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_getdnames(cmount, dirp, NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_telldir(cmount, dirp)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_link(cmount, "/", "/link")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_unlink(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_rename(cmount, "/path", "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_mkdir(cmount, "/", 0655)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_mkdirs(cmount, "/", 0655)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_rmdir(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_readlink(cmount, "/path", NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_symlink(cmount, "/path", "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_statx(cmount, "/path", &stx, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_setattrx(cmount, "/path", &stx, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_getxattr(cmount, "/path", "name", NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_lgetxattr(cmount, "/path", "name", NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_listxattr(cmount, "/path", NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_llistxattr(cmount, "/path", NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_removexattr(cmount, "/path", "name")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_lremovexattr(cmount, "/path", "name")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_setxattr(cmount, "/path", "name", NULL, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_lsetxattr(cmount, "/path", "name", NULL, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_fsetattrx(cmount, 0, &stx, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_chmod(cmount, "/path", 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_fchmod(cmount, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_chown(cmount, "/path", 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_lchown(cmount, "/path", 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_fchown(cmount, 0, 0, 0)); + + struct utimbuf utb; + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_utime(cmount, "/path", &utb)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_truncate(cmount, "/path", 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_mknod(cmount, "/path", 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_open(cmount, "/path", 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_open_layout(cmount, "/path", 0, 0, 0, 0, 0, "pool")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_close(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_lseek(cmount, 0, 0, SEEK_SET)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_read(cmount, 0, NULL, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_write(cmount, 0, NULL, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_ftruncate(cmount, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_fsync(cmount, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_fstatx(cmount, 0, &stx, 0, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_sync_fs(cmount)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_stripe_unit(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_stripe_count(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_layout(cmount, 0, NULL, NULL ,NULL ,NULL)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_object_size(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_pool(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_pool_name(cmount, 0, NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_replication(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_replication(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_layout(cmount, "/path", NULL, NULL, NULL, NULL)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_object_size(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_stripe_count(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_stripe_unit(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_pool(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_path_pool_name(cmount, "/path", NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_pool_name(cmount, 0, NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_stripe_address(cmount, 0, 0, NULL, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_localize_reads(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_debug_get_fd_caps(cmount, 0)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_debug_get_file_caps(cmount, "/path")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_stripe_unit_granularity(cmount)); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_pool_id(cmount, "data")); + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_pool_replication(cmount, 1)); + + ceph_release(cmount); +} + +TEST(LibCephFS, GetPoolId) { + 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 name[80]; + memset(name, 0, sizeof(name)); + ASSERT_LE(0, ceph_get_path_pool_name(cmount, "/", name, sizeof(name))); + ASSERT_GE(ceph_get_pool_id(cmount, name), 0); + ASSERT_EQ(ceph_get_pool_id(cmount, "weflkjwelfjwlkejf"), -CEPHFS_ENOENT); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetPoolReplication) { + 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); + + /* negative pools */ + ASSERT_EQ(ceph_get_pool_replication(cmount, -10), -CEPHFS_ENOENT); + + /* valid pool */ + int pool_id; + int stripe_unit, stripe_count, object_size; + ASSERT_EQ(0, ceph_get_path_layout(cmount, "/", &stripe_unit, &stripe_count, + &object_size, &pool_id)); + ASSERT_GE(pool_id, 0); + ASSERT_GT(ceph_get_pool_replication(cmount, pool_id), 0); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetExtentOsds) { + struct ceph_mount_info *cmount; + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_file_extent_osds(cmount, 0, 0, NULL, 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); + + int stripe_unit = (1<<18); + + /* make a file! */ + char test_file[256]; + sprintf(test_file, "test_extent_osds_%d", getpid()); + int fd = ceph_open_layout(cmount, test_file, O_CREAT|O_RDWR, 0666, + stripe_unit, 2, stripe_unit*2, NULL); + ASSERT_GT(fd, 0); + + /* get back how many osds > 0 */ + int ret = ceph_get_file_extent_osds(cmount, fd, 0, NULL, NULL, 0); + EXPECT_GT(ret, 0); + + int64_t len; + int osds[ret]; + + /* full stripe extent */ + EXPECT_EQ(ret, ceph_get_file_extent_osds(cmount, fd, 0, &len, osds, ret)); + EXPECT_EQ(len, (int64_t)stripe_unit); + + /* half stripe extent */ + EXPECT_EQ(ret, ceph_get_file_extent_osds(cmount, fd, stripe_unit/2, &len, osds, ret)); + EXPECT_EQ(len, (int64_t)stripe_unit/2); + + /* 1.5 stripe unit offset -1 byte */ + EXPECT_EQ(ret, ceph_get_file_extent_osds(cmount, fd, 3*stripe_unit/2-1, &len, osds, ret)); + EXPECT_EQ(len, (int64_t)stripe_unit/2+1); + + /* 1.5 stripe unit offset +1 byte */ + EXPECT_EQ(ret, ceph_get_file_extent_osds(cmount, fd, 3*stripe_unit/2+1, &len, osds, ret)); + EXPECT_EQ(len, (int64_t)stripe_unit/2-1); + + /* only when more than 1 osd */ + if (ret > 1) { + EXPECT_EQ(-CEPHFS_ERANGE, ceph_get_file_extent_osds(cmount, fd, 0, NULL, osds, 1)); + } + + ceph_close(cmount, fd); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetOsdCrushLocation) { + struct ceph_mount_info *cmount; + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_osd_crush_location(cmount, 0, 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); + + ASSERT_EQ(ceph_get_osd_crush_location(cmount, 0, NULL, 1), -CEPHFS_EINVAL); + + char path[256]; + ASSERT_EQ(ceph_get_osd_crush_location(cmount, 9999999, path, 0), -CEPHFS_ENOENT); + ASSERT_EQ(ceph_get_osd_crush_location(cmount, -1, path, 0), -CEPHFS_EINVAL); + + char test_file[256]; + sprintf(test_file, "test_osds_loc_%d", getpid()); + int fd = ceph_open(cmount, test_file, O_CREAT|O_RDWR, 0666); + ASSERT_GT(fd, 0); + + /* get back how many osds > 0 */ + int ret = ceph_get_file_extent_osds(cmount, fd, 0, NULL, NULL, 0); + EXPECT_GT(ret, 0); + + /* full stripe extent */ + int osds[ret]; + EXPECT_EQ(ret, ceph_get_file_extent_osds(cmount, fd, 0, NULL, osds, ret)); + + ASSERT_GT(ceph_get_osd_crush_location(cmount, 0, path, 0), 0); + ASSERT_EQ(ceph_get_osd_crush_location(cmount, 0, path, 1), -CEPHFS_ERANGE); + + for (int i = 0; i < ret; i++) { + int len = ceph_get_osd_crush_location(cmount, osds[i], path, sizeof(path)); + ASSERT_GT(len, 0); + int pos = 0; + while (pos < len) { + std::string type(path + pos); + ASSERT_GT((int)type.size(), 0); + pos += type.size() + 1; + + std::string name(path + pos); + ASSERT_GT((int)name.size(), 0); + pos += name.size() + 1; + } + } + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetOsdAddr) { + struct ceph_mount_info *cmount; + ASSERT_EQ(ceph_create(&cmount, NULL), 0); + + EXPECT_EQ(-CEPHFS_ENOTCONN, ceph_get_osd_addr(cmount, 0, NULL)); + + 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); + + ASSERT_EQ(-CEPHFS_EINVAL, ceph_get_osd_addr(cmount, 0, NULL)); + + struct sockaddr_storage addr; + ASSERT_EQ(-CEPHFS_ENOENT, ceph_get_osd_addr(cmount, -1, &addr)); + ASSERT_EQ(-CEPHFS_ENOENT, ceph_get_osd_addr(cmount, 9999999, &addr)); + + ASSERT_EQ(0, ceph_get_osd_addr(cmount, 0, &addr)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, OpenNoClose) { + 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, "/"), 0); + + pid_t mypid = getpid(); + char str_buf[256]; + sprintf(str_buf, "open_no_close_dir%d", mypid); + ASSERT_EQ(0, ceph_mkdirs(cmount, str_buf, 0777)); + + struct ceph_dir_result *ls_dir = NULL; + ASSERT_EQ(ceph_opendir(cmount, str_buf, &ls_dir), 0); + + sprintf(str_buf, "open_no_close_file%d", mypid); + int fd = ceph_open(cmount, str_buf, O_RDONLY|O_CREAT, 0666); + ASSERT_LT(0, fd); + + // shutdown should force close opened file/dir + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Nlink) { + 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, "/"), 0); + + Inode *root, *dir, *file; + + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + + char dirname[32], filename[32], linkname[32]; + sprintf(dirname, "nlinkdir%x", getpid()); + sprintf(filename, "nlinkorig%x", getpid()); + sprintf(linkname, "nlinklink%x", getpid()); + + struct ceph_statx stx; + Fh *fh; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_mkdir(cmount, root, dirname, 0755, &dir, &stx, 0, 0, perms), 0); + ASSERT_EQ(ceph_ll_create(cmount, dir, filename, 0666, O_RDWR|O_CREAT|O_EXCL, + &file, &fh, &stx, CEPH_STATX_NLINK, 0, perms), 0); + ASSERT_EQ(ceph_ll_close(cmount, fh), 0); + ASSERT_EQ(stx.stx_nlink, (nlink_t)1); + + ASSERT_EQ(ceph_ll_link(cmount, file, dir, linkname, perms), 0); + ASSERT_EQ(ceph_ll_getattr(cmount, file, &stx, CEPH_STATX_NLINK, 0, perms), 0); + ASSERT_EQ(stx.stx_nlink, (nlink_t)2); + + ASSERT_EQ(ceph_ll_unlink(cmount, dir, linkname, perms), 0); + ASSERT_EQ(ceph_ll_lookup(cmount, dir, filename, &file, &stx, + CEPH_STATX_NLINK, 0, perms), 0); + ASSERT_EQ(stx.stx_nlink, (nlink_t)1); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SlashDotDot) { + 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, "/"), 0); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, "/.", &stx, CEPH_STATX_INO, 0), 0); + + ino_t ino = stx.stx_ino; + ASSERT_EQ(ceph_statx(cmount, "/..", &stx, CEPH_STATX_INO, 0), 0); + + /* At root, "." and ".." should be the same inode */ + ASSERT_EQ(ino, stx.stx_ino); + + /* Test accessing the parent of an unlinked directory */ + char dir1[32], dir2[56]; + sprintf(dir1, "/sldotdot%x", getpid()); + sprintf(dir2, "%s/sub%x", dir1, getpid()); + + ASSERT_EQ(ceph_mkdir(cmount, dir1, 0755), 0); + ASSERT_EQ(ceph_mkdir(cmount, dir2, 0755), 0); + + ASSERT_EQ(ceph_chdir(cmount, dir2), 0); + + /* Test behavior when unlinking cwd */ + struct ceph_dir_result *rdir; + ASSERT_EQ(ceph_opendir(cmount, ".", &rdir), 0); + ASSERT_EQ(ceph_rmdir(cmount, dir2), 0); + + /* get "." entry */ + struct dirent *result = ceph_readdir(cmount, rdir); + ino = result->d_ino; + + /* get ".." entry */ + result = ceph_readdir(cmount, rdir); + ASSERT_EQ(ino, result->d_ino); + ceph_closedir(cmount, rdir); + + /* Make sure it works same way when mounting subtree */ + ASSERT_EQ(ceph_unmount(cmount), 0); + ASSERT_EQ(ceph_mount(cmount, dir1), 0); + ASSERT_EQ(ceph_statx(cmount, "/..", &stx, CEPH_STATX_INO, 0), 0); + + /* Test readdir behavior */ + ASSERT_EQ(ceph_opendir(cmount, "/", &rdir), 0); + result = ceph_readdir(cmount, rdir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + ino = result->d_ino; + result = ceph_readdir(cmount, rdir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + ASSERT_EQ(ino, result->d_ino); + + ceph_shutdown(cmount); +} + +static inline bool +timespec_eq(timespec const& lhs, timespec const& rhs) +{ + return lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec == rhs.tv_nsec; +} + +TEST(LibCephFS, Btime) { + 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, "/"), 0); + + char filename[32]; + sprintf(filename, "/getattrx%x", getpid()); + + ceph_unlink(cmount, filename); + int fd = ceph_open(cmount, filename, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + + /* make sure fstatx works */ + struct ceph_statx stx; + + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_CTIME|CEPH_STATX_BTIME, 0), 0); + ASSERT_TRUE(stx.stx_mask & (CEPH_STATX_CTIME|CEPH_STATX_BTIME)); + ASSERT_TRUE(timespec_eq(stx.stx_ctime, stx.stx_btime)); + ceph_close(cmount, fd); + + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_CTIME|CEPH_STATX_BTIME, 0), 0); + ASSERT_TRUE(timespec_eq(stx.stx_ctime, stx.stx_btime)); + ASSERT_TRUE(stx.stx_mask & (CEPH_STATX_CTIME|CEPH_STATX_BTIME)); + + struct timespec old_btime = stx.stx_btime; + + /* Now sleep, do a chmod and verify that the ctime changed, but btime didn't */ + sleep(1); + ASSERT_EQ(ceph_chmod(cmount, filename, 0644), 0); + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_CTIME|CEPH_STATX_BTIME, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_BTIME); + ASSERT_TRUE(timespec_eq(stx.stx_btime, old_btime)); + ASSERT_FALSE(timespec_eq(stx.stx_ctime, stx.stx_btime)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SetBtime) { + 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, "/"), 0); + + char filename[32]; + sprintf(filename, "/setbtime%x", getpid()); + + ceph_unlink(cmount, filename); + int fd = ceph_open(cmount, filename, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + ceph_close(cmount, fd); + + struct ceph_statx stx; + struct timespec old_btime = { 1, 2 }; + + stx.stx_btime = old_btime; + + ASSERT_EQ(ceph_setattrx(cmount, filename, &stx, CEPH_SETATTR_BTIME, 0), 0); + + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_BTIME, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_BTIME); + ASSERT_TRUE(timespec_eq(stx.stx_btime, old_btime)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LazyStatx) { + struct ceph_mount_info *cmount1, *cmount2; + ASSERT_EQ(ceph_create(&cmount1, NULL), 0); + ASSERT_EQ(ceph_create(&cmount2, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount1, NULL), 0); + ASSERT_EQ(ceph_conf_read_file(cmount2, NULL), 0); + ASSERT_EQ(0, ceph_conf_parse_env(cmount1, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount2, NULL)); + ASSERT_EQ(ceph_mount(cmount1, "/"), 0); + ASSERT_EQ(ceph_mount(cmount2, "/"), 0); + + char filename[32]; + sprintf(filename, "lazystatx%x", getpid()); + + Inode *root1, *file1, *root2, *file2; + struct ceph_statx stx; + Fh *fh; + UserPerm *perms1 = ceph_mount_perms(cmount1); + UserPerm *perms2 = ceph_mount_perms(cmount2); + + ASSERT_EQ(ceph_ll_lookup_root(cmount1, &root1), 0); + ceph_ll_unlink(cmount1, root1, filename, perms1); + ASSERT_EQ(ceph_ll_create(cmount1, root1, filename, 0666, O_RDWR|O_CREAT|O_EXCL, + &file1, &fh, &stx, 0, 0, perms1), 0); + ASSERT_EQ(ceph_ll_close(cmount1, fh), 0); + + ASSERT_EQ(ceph_ll_lookup_root(cmount2, &root2), 0); + + ASSERT_EQ(ceph_ll_lookup(cmount2, root2, filename, &file2, &stx, CEPH_STATX_CTIME, 0, perms2), 0); + + struct timespec old_ctime = stx.stx_ctime; + + /* + * Now sleep, do a chmod on the first client and the see whether we get a + * different ctime with a statx that uses AT_STATX_DONT_SYNC + */ + sleep(1); + stx.stx_mode = 0644; + ASSERT_EQ(ceph_ll_setattr(cmount1, file1, &stx, CEPH_SETATTR_MODE, perms1), 0); + + ASSERT_EQ(ceph_ll_getattr(cmount2, file2, &stx, CEPH_STATX_CTIME, AT_STATX_DONT_SYNC, perms2), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_CTIME); + ASSERT_TRUE(stx.stx_ctime.tv_sec == old_ctime.tv_sec && + stx.stx_ctime.tv_nsec == old_ctime.tv_nsec); + + ceph_shutdown(cmount1); + ceph_shutdown(cmount2); +} + +TEST(LibCephFS, ChangeAttr) { + 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, "/"), 0); + + char filename[32]; + sprintf(filename, "/changeattr%x", getpid()); + + ceph_unlink(cmount, filename); + int fd = ceph_open(cmount, filename, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + + uint64_t old_change_attr = stx.stx_version; + + /* do chmod, and check whether change_attr changed */ + ASSERT_EQ(ceph_chmod(cmount, filename, 0644), 0); + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_NE(stx.stx_version, old_change_attr); + old_change_attr = stx.stx_version; + + /* now do a write and see if it changed again */ + ASSERT_EQ(3, ceph_write(cmount, fd, "foo", 3, 0)); + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_NE(stx.stx_version, old_change_attr); + old_change_attr = stx.stx_version; + + /* Now truncate and check again */ + ASSERT_EQ(0, ceph_ftruncate(cmount, fd, 0)); + ASSERT_EQ(ceph_statx(cmount, filename, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_NE(stx.stx_version, old_change_attr); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DirChangeAttrCreateFile) { + 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, "/"), 0); + + char dirpath[32], filepath[56]; + sprintf(dirpath, "/dirchange%x", getpid()); + sprintf(filepath, "%s/foo", dirpath); + + ASSERT_EQ(ceph_mkdir(cmount, dirpath, 0755), 0); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + + uint64_t old_change_attr = stx.stx_version; + + /* Important: Follow an operation that changes the directory's ctime (setxattr) + * with one that changes the directory's mtime and ctime (create). + * Check that directory's change_attr is updated everytime ctime changes. + */ + + /* set xattr on dir, and check whether dir's change_attr is incremented */ + ASSERT_EQ(ceph_setxattr(cmount, dirpath, "user.name", (void*)"bob", 3, XATTR_CREATE), 0); + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_GT(stx.stx_version, old_change_attr); + old_change_attr = stx.stx_version; + + /* create a file within dir, and check whether dir's change_attr is incremented */ + int fd = ceph_open(cmount, filepath, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + ceph_close(cmount, fd); + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_GT(stx.stx_version, old_change_attr); + + ASSERT_EQ(0, ceph_unlink(cmount, filepath)); + ASSERT_EQ(0, ceph_rmdir(cmount, dirpath)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DirChangeAttrRenameFile) { + 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, "/"), 0); + + char dirpath[32], filepath[56], newfilepath[56]; + sprintf(dirpath, "/dirchange%x", getpid()); + sprintf(filepath, "%s/foo", dirpath); + + ASSERT_EQ(ceph_mkdir(cmount, dirpath, 0755), 0); + + int fd = ceph_open(cmount, filepath, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + ceph_close(cmount, fd); + + /* Important: Follow an operation that changes the directory's ctime (setattr) + * with one that changes the directory's mtime and ctime (rename). + * Check that directory's change_attr is updated everytime ctime changes. + */ + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + + uint64_t old_change_attr = stx.stx_version; + + /* chmod dir, and check whether dir's change_attr is incremented */ + ASSERT_EQ(ceph_chmod(cmount, dirpath, 0777), 0); + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_GT(stx.stx_version, old_change_attr); + old_change_attr = stx.stx_version; + + /* rename a file within dir, and check whether dir's change_attr is incremented */ + sprintf(newfilepath, "%s/bar", dirpath); + ASSERT_EQ(ceph_rename(cmount, filepath, newfilepath), 0); + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_GT(stx.stx_version, old_change_attr); + + ASSERT_EQ(0, ceph_unlink(cmount, newfilepath)); + ASSERT_EQ(0, ceph_rmdir(cmount, dirpath)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, DirChangeAttrRemoveFile) { + 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, "/"), 0); + + char dirpath[32], filepath[56]; + sprintf(dirpath, "/dirchange%x", getpid()); + sprintf(filepath, "%s/foo", dirpath); + + ASSERT_EQ(ceph_mkdir(cmount, dirpath, 0755), 0); + + ASSERT_EQ(ceph_setxattr(cmount, dirpath, "user.name", (void*)"bob", 3, XATTR_CREATE), 0); + + int fd = ceph_open(cmount, filepath, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + ceph_close(cmount, fd); + + /* Important: Follow an operation that changes the directory's ctime (removexattr) + * with one that changes the directory's mtime and ctime (remove a file). + * Check that directory's change_attr is updated everytime ctime changes. + */ + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + + uint64_t old_change_attr = stx.stx_version; + + /* remove xattr, and check whether dir's change_attr is incremented */ + ASSERT_EQ(ceph_removexattr(cmount, dirpath, "user.name"), 0); + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_GT(stx.stx_version, old_change_attr); + old_change_attr = stx.stx_version; + + /* remove a file within dir, and check whether dir's change_attr is incremented */ + ASSERT_EQ(0, ceph_unlink(cmount, filepath)); + ASSERT_EQ(ceph_statx(cmount, dirpath, &stx, CEPH_STATX_VERSION, 0), 0); + ASSERT_TRUE(stx.stx_mask & CEPH_STATX_VERSION); + ASSERT_GT(stx.stx_version, old_change_attr); + + ASSERT_EQ(0, ceph_rmdir(cmount, dirpath)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SetSize) { + 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, "/"), 0); + + char filename[32]; + sprintf(filename, "/setsize%x", getpid()); + + ceph_unlink(cmount, filename); + int fd = ceph_open(cmount, filename, O_RDWR|O_CREAT|O_EXCL, 0666); + ASSERT_LT(0, fd); + + struct ceph_statx stx; + uint64_t size = 8388608; + stx.stx_size = size; + ASSERT_EQ(ceph_fsetattrx(cmount, fd, &stx, CEPH_SETATTR_SIZE), 0); + ASSERT_EQ(ceph_fstatx(cmount, fd, &stx, CEPH_STATX_SIZE, 0), 0); + ASSERT_EQ(stx.stx_size, size); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, OperationsOnRoot) +{ + 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, "/"), 0); + + char dirname[32]; + sprintf(dirname, "/somedir%x", getpid()); + + ASSERT_EQ(ceph_mkdir(cmount, dirname, 0755), 0); + + ASSERT_EQ(ceph_rmdir(cmount, "/"), -CEPHFS_EBUSY); + + ASSERT_EQ(ceph_link(cmount, "/", "/"), -CEPHFS_EEXIST); + ASSERT_EQ(ceph_link(cmount, dirname, "/"), -CEPHFS_EEXIST); + ASSERT_EQ(ceph_link(cmount, "nonExisitingDir", "/"), -CEPHFS_ENOENT); + + ASSERT_EQ(ceph_unlink(cmount, "/"), -CEPHFS_EISDIR); + + ASSERT_EQ(ceph_rename(cmount, "/", "/"), -CEPHFS_EBUSY); + ASSERT_EQ(ceph_rename(cmount, dirname, "/"), -CEPHFS_EBUSY); + ASSERT_EQ(ceph_rename(cmount, "nonExistingDir", "/"), -CEPHFS_EBUSY); + ASSERT_EQ(ceph_rename(cmount, "/", dirname), -CEPHFS_EBUSY); + ASSERT_EQ(ceph_rename(cmount, "/", "nonExistingDir"), -CEPHFS_EBUSY); + + ASSERT_EQ(ceph_mkdir(cmount, "/", 0777), -CEPHFS_EEXIST); + + ASSERT_EQ(ceph_mknod(cmount, "/", 0, 0), -CEPHFS_EEXIST); + + ASSERT_EQ(ceph_symlink(cmount, "/", "/"), -CEPHFS_EEXIST); + ASSERT_EQ(ceph_symlink(cmount, dirname, "/"), -CEPHFS_EEXIST); + ASSERT_EQ(ceph_symlink(cmount, "nonExistingDir", "/"), -CEPHFS_EEXIST); + + ceph_shutdown(cmount); +} + +// no rlimits on Windows +#ifndef _WIN32 +static void shutdown_racer_func() +{ + const int niter = 32; + struct ceph_mount_info *cmount; + int i; + + for (i = 0; i < niter; ++i) { + 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, "/"), 0); + ceph_shutdown(cmount); + } +} + +// See tracker #20988 +TEST(LibCephFS, ShutdownRace) +{ + const int nthreads = 32; + std::thread threads[nthreads]; + + // Need a bunch of fd's for this test + struct rlimit rold, rnew; + ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &rold), 0); + rnew = rold; + rnew.rlim_cur = rnew.rlim_max; + + cout << "Setting RLIMIT_NOFILE from " << rold.rlim_cur << + " to " << rnew.rlim_cur << std::endl; + + ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rnew), 0); + + for (int i = 0; i < nthreads; ++i) + threads[i] = std::thread(shutdown_racer_func); + + for (int i = 0; i < nthreads; ++i) + threads[i].join(); + /* + * Let's just ignore restoring the open files limit, + * the kernel will defer releasing the file descriptors + * and then the process will be possibly reachthe open + * files limit. More detail, please see tracer#43039 + */ +// ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rold), 0); +} +#endif + +static void get_current_time_utimbuf(struct utimbuf *utb) +{ + utime_t t = ceph_clock_now(); + utb->actime = t.sec(); + utb->modtime = t.sec(); +} + +static void get_current_time_timeval(struct timeval tv[2]) +{ + utime_t t = ceph_clock_now(); + t.copy_to_timeval(&tv[0]); + t.copy_to_timeval(&tv[1]); +} + +static void get_current_time_timespec(struct timespec ts[2]) +{ + utime_t t = ceph_clock_now(); + t.to_timespec(&ts[0]); + t.to_timespec(&ts[1]); +} + +TEST(LibCephFS, TestUtime) { + 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_file[256]; + sprintf(test_file, "test_utime_file_%d", getpid()); + int fd = ceph_open(cmount, test_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + + struct utimbuf utb; + struct ceph_statx stx; + + get_current_time_utimbuf(&utb); + + // ceph_utime() + EXPECT_EQ(0, ceph_utime(cmount, test_file, &utb)); + ASSERT_EQ(ceph_statx(cmount, test_file, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(utb.actime, 0)); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(utb.modtime, 0)); + + get_current_time_utimbuf(&utb); + + // ceph_futime() + EXPECT_EQ(0, ceph_futime(cmount, fd, &utb)); + ASSERT_EQ(ceph_statx(cmount, test_file, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(utb.actime, 0)); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(utb.modtime, 0)); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, TestUtimes) { + 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_file[256]; + char test_symlink[256]; + + sprintf(test_file, "test_utimes_file_%d", getpid()); + sprintf(test_symlink, "test_utimes_symlink_%d", getpid()); + int fd = ceph_open(cmount, test_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + + ASSERT_EQ(ceph_symlink(cmount, test_file, test_symlink), 0); + + struct timeval times[2]; + struct ceph_statx stx; + + get_current_time_timeval(times); + + // ceph_utimes() on symlink, validate target file time + EXPECT_EQ(0, ceph_utimes(cmount, test_symlink, times)); + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(times[0])); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(times[1])); + + get_current_time_timeval(times); + + // ceph_lutimes() on symlink, validate symlink time + EXPECT_EQ(0, ceph_lutimes(cmount, test_symlink, times)); + ASSERT_EQ(ceph_statx(cmount, test_symlink, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, AT_SYMLINK_NOFOLLOW), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(times[0])); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(times[1])); + + get_current_time_timeval(times); + + // ceph_futimes() + EXPECT_EQ(0, ceph_futimes(cmount, fd, times)); + ASSERT_EQ(ceph_statx(cmount, test_file, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(times[0])); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(times[1])); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, TestFutimens) { + 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_file[256]; + + sprintf(test_file, "test_futimens_file_%d", getpid()); + int fd = ceph_open(cmount, test_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + + struct timespec times[2]; + struct ceph_statx stx; + + get_current_time_timespec(times); + + // ceph_futimens() + EXPECT_EQ(0, ceph_futimens(cmount, fd, times)); + ASSERT_EQ(ceph_statx(cmount, test_file, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(times[0])); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(times[1])); + + ceph_close(cmount, fd); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, OperationsOnDotDot) { + 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 c_dir[512], c_dir_dot[1024], c_dir_dotdot[1024]; + char c_non_existent_dir[1024], c_non_existent_dirs[1024]; + char c_temp[1024]; + + pid_t mypid = getpid(); + sprintf(c_dir, "/oodd_dir_%d", mypid); + sprintf(c_dir_dot, "/%s/.", c_dir); + sprintf(c_dir_dotdot, "/%s/..", c_dir); + sprintf(c_non_existent_dir, "/%s/../oodd_nonexistent/..", c_dir); + sprintf(c_non_existent_dirs, + "/%s/../ood_nonexistent1_%d/oodd_nonexistent2_%d", c_dir, mypid, mypid); + sprintf(c_temp, "/oodd_temp_%d", mypid); + + ASSERT_EQ(0, ceph_mkdir(cmount, c_dir, 0777)); + ASSERT_EQ(-CEPHFS_EEXIST, ceph_mkdir(cmount, c_dir_dot, 0777)); + ASSERT_EQ(-CEPHFS_EEXIST, ceph_mkdir(cmount, c_dir_dotdot, 0777)); + ASSERT_EQ(0, ceph_mkdirs(cmount, c_non_existent_dirs, 0777)); + + ASSERT_EQ(-CEPHFS_ENOTEMPTY, ceph_rmdir(cmount, c_dir_dot)); + ASSERT_EQ(-CEPHFS_ENOTEMPTY, ceph_rmdir(cmount, c_dir_dotdot)); + // non existent directory should return -CEPHFS_ENOENT + ASSERT_EQ(-CEPHFS_ENOENT, ceph_rmdir(cmount, c_non_existent_dir)); + + ASSERT_EQ(-CEPHFS_EBUSY, ceph_rename(cmount, c_dir_dot, c_temp)); + ASSERT_EQ(0, ceph_chdir(cmount, c_dir)); + ASSERT_EQ(0, ceph_mkdir(cmount, c_temp, 0777)); + ASSERT_EQ(-CEPHFS_EBUSY, ceph_rename(cmount, c_temp, "..")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Caps_vxattr) { + 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_caps_vxattr_file[256]; + char gxattrv[128]; + int xbuflen = sizeof(gxattrv); + pid_t mypid = getpid(); + + sprintf(test_caps_vxattr_file, "test_caps_vxattr_%d", mypid); + int fd = ceph_open(cmount, test_caps_vxattr_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + ceph_close(cmount, fd); + + int alen = ceph_getxattr(cmount, test_caps_vxattr_file, "ceph.caps", (void *)gxattrv, xbuflen); + ASSERT_GT(alen, 0); + gxattrv[alen] = '\0'; + + char caps_regex[] = "pA[sx]*L[sx]*X[sx]*F[sxcrwbal]*/0x[0-9a-fA-f]+"; + ASSERT_TRUE(regex_match(gxattrv, regex(caps_regex)) == 1); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SnapXattrs) { + 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_snap_xattr_file[256]; + char c_temp[PATH_MAX]; + char gxattrv[128]; + char gxattrv2[128]; + int xbuflen = sizeof(gxattrv); + pid_t mypid = getpid(); + + sprintf(test_snap_xattr_file, "test_snap_xattr_%d", mypid); + int fd = ceph_open(cmount, test_snap_xattr_file, O_CREAT, 0666); + ASSERT_GT(fd, 0); + ceph_close(cmount, fd); + + sprintf(c_temp, "/.snap/test_snap_xattr_snap_%d", mypid); + ASSERT_EQ(0, ceph_mkdir(cmount, c_temp, 0777)); + + int alen = ceph_getxattr(cmount, c_temp, "ceph.snap.btime", (void *)gxattrv, xbuflen); + // xattr value is secs.nsecs (don't assume zero-term) + ASSERT_LT(0, alen); + ASSERT_LT(alen, xbuflen); + gxattrv[alen] = '\0'; + char *s = strchr(gxattrv, '.'); + char *q = NULL; + ASSERT_NE(q, s); + ASSERT_LT(s, gxattrv + alen); + ASSERT_EQ('.', *s); + *s = '\0'; + utime_t btime = utime_t(strtoull(gxattrv, NULL, 10), strtoull(s + 1, NULL, 10)); + *s = '.'; // restore for later strcmp + + // file within the snapshot should carry the same btime + sprintf(c_temp, "/.snap/test_snap_xattr_snap_%d/%s", mypid, test_snap_xattr_file); + + int alen2 = ceph_getxattr(cmount, c_temp, "ceph.snap.btime", (void *)gxattrv2, xbuflen); + ASSERT_EQ(alen, alen2); + ASSERT_EQ(0, strncmp(gxattrv, gxattrv2, alen)); + + // non-snap file shouldn't carry the xattr + alen = ceph_getxattr(cmount, test_snap_xattr_file, "ceph.snap.btime", (void *)gxattrv2, xbuflen); + ASSERT_EQ(-CEPHFS_ENODATA, alen); + + // create a second snapshot + sprintf(c_temp, "/.snap/test_snap_xattr_snap2_%d", mypid); + ASSERT_EQ(0, ceph_mkdir(cmount, c_temp, 0777)); + + // check that the btime for the newer snapshot is > older + alen = ceph_getxattr(cmount, c_temp, "ceph.snap.btime", (void *)gxattrv2, xbuflen); + ASSERT_LT(0, alen); + ASSERT_LT(alen, xbuflen); + gxattrv2[alen] = '\0'; + s = strchr(gxattrv2, '.'); + ASSERT_NE(q, s); + ASSERT_LT(s, gxattrv2 + alen); + ASSERT_EQ('.', *s); + *s = '\0'; + utime_t new_btime = utime_t(strtoull(gxattrv2, NULL, 10), strtoull(s + 1, NULL, 10)); + #ifndef _WIN32 + // This assertion sometimes fails on Windows, possibly due to the clock precision. + ASSERT_LT(btime, new_btime); + #endif + + // listxattr() shouldn't return snap.btime vxattr + char xattrlist[512]; + int len = ceph_listxattr(cmount, test_snap_xattr_file, xattrlist, sizeof(xattrlist)); + ASSERT_GE(sizeof(xattrlist), (size_t)len); + char *p = xattrlist; + int found = 0; + while (len > 0) { + if (strcmp(p, "ceph.snap.btime") == 0) + found++; + len -= strlen(p) + 1; + p += strlen(p) + 1; + } + ASSERT_EQ(found, 0); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Lseek) { + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + char c_path[1024]; + sprintf(c_path, "test_lseek_%d", getpid()); + int fd = ceph_open(cmount, c_path, O_RDWR|O_CREAT|O_TRUNC, 0666); + ASSERT_LT(0, fd); + + const char *out_buf = "hello world"; + size_t size = strlen(out_buf); + ASSERT_EQ(ceph_write(cmount, fd, out_buf, size, 0), (int)size); + + /* basic SEEK_SET/END/CUR tests */ + ASSERT_EQ(0, ceph_lseek(cmount, fd, 0, SEEK_SET)); + ASSERT_EQ(size, ceph_lseek(cmount, fd, 0, SEEK_END)); + ASSERT_EQ(0, ceph_lseek(cmount, fd, -size, SEEK_CUR)); + + /* Test basic functionality and out of bounds conditions for SEEK_HOLE/DATA */ +#ifdef SEEK_HOLE + ASSERT_EQ(size, ceph_lseek(cmount, fd, 0, SEEK_HOLE)); + ASSERT_EQ(-CEPHFS_ENXIO, ceph_lseek(cmount, fd, -1, SEEK_HOLE)); + ASSERT_EQ(-CEPHFS_ENXIO, ceph_lseek(cmount, fd, size + 1, SEEK_HOLE)); +#endif +#ifdef SEEK_DATA + ASSERT_EQ(0, ceph_lseek(cmount, fd, 0, SEEK_DATA)); + ASSERT_EQ(-CEPHFS_ENXIO, ceph_lseek(cmount, fd, -1, SEEK_DATA)); + ASSERT_EQ(-CEPHFS_ENXIO, ceph_lseek(cmount, fd, size + 1, SEEK_DATA)); +#endif + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SnapInfoOnNonSnapshot) { + 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); + + struct snap_info info; + ASSERT_EQ(-CEPHFS_EINVAL, ceph_get_snap_info(cmount, "/", &info)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, EmptySnapInfo) { + 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 dir_path[64]; + char snap_path[PATH_MAX]; + sprintf(dir_path, "/dir0_%d", getpid()); + sprintf(snap_path, "%s/.snap/snap0_%d", dir_path, getpid()); + + ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755)); + // snapshot without custom metadata + ASSERT_EQ(0, ceph_mkdir(cmount, snap_path, 0755)); + + struct snap_info info; + ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info)); + ASSERT_GT(info.id, 0); + ASSERT_EQ(info.nr_snap_metadata, 0); + + ASSERT_EQ(0, ceph_rmdir(cmount, snap_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SnapInfo) { + 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 dir_path[64]; + char snap_name[64]; + char snap_path[PATH_MAX]; + sprintf(dir_path, "/dir0_%d", getpid()); + sprintf(snap_name, "snap0_%d", getpid()); + sprintf(snap_path, "%s/.snap/%s", dir_path, snap_name); + + ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755)); + // snapshot with custom metadata + struct snap_metadata snap_meta[] = {{"foo", "bar"},{"this", "that"},{"abcdefg", "12345"}}; + ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, snap_meta, std::size(snap_meta))); + + struct snap_info info; + ASSERT_EQ(0, ceph_get_snap_info(cmount, snap_path, &info)); + ASSERT_GT(info.id, 0); + ASSERT_EQ(info.nr_snap_metadata, std::size(snap_meta)); + for (size_t i = 0; i < info.nr_snap_metadata; ++i) { + auto &k1 = info.snap_metadata[i].key; + auto &v1 = info.snap_metadata[i].value; + bool found = false; + for (size_t j = 0; j < info.nr_snap_metadata; ++j) { + auto &k2 = snap_meta[j].key; + auto &v2 = snap_meta[j].value; + if (strncmp(k1, k2, strlen(k1)) == 0 && strncmp(v1, v2, strlen(v1)) == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + } + ceph_free_snap_info_buffer(&info); + + ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LookupInoMDSDir) { + 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); + + Inode *inode; + auto ino = inodeno_t(0x100); /* rank 0 ~mdsdir */ + ASSERT_EQ(-CEPHFS_ESTALE, ceph_ll_lookup_inode(cmount, ino, &inode)); + ino = inodeno_t(0x600); /* rank 0 first stray dir */ + ASSERT_EQ(-CEPHFS_ESTALE, ceph_ll_lookup_inode(cmount, ino, &inode)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LookupVino) { + 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 dir_path[64]; + char snap_name[64]; + char snapdir_path[128]; + char snap_path[256]; + char file_path[PATH_MAX]; + char snap_file[PATH_MAX]; + sprintf(dir_path, "/dir0_%d", getpid()); + sprintf(snap_name, "snap0_%d", getpid()); + sprintf(file_path, "%s/file_%d", dir_path, getpid()); + sprintf(snapdir_path, "%s/.snap", dir_path); + sprintf(snap_path, "%s/%s", snapdir_path, snap_name); + sprintf(snap_file, "%s/file_%d", snap_path, getpid()); + + ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755)); + int fd = ceph_open(cmount, file_path, O_WRONLY|O_CREAT, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_mksnap(cmount, dir_path, snap_name, 0755, nullptr, 0)); + + // record vinos for all of them + struct ceph_statx stx; + ASSERT_EQ(0, ceph_statx(cmount, dir_path, &stx, CEPH_STATX_INO, 0)); + vinodeno_t dir_vino(stx.stx_ino, stx.stx_dev); + + ASSERT_EQ(0, ceph_statx(cmount, file_path, &stx, CEPH_STATX_INO, 0)); + vinodeno_t file_vino(stx.stx_ino, stx.stx_dev); + + ASSERT_EQ(0, ceph_statx(cmount, snapdir_path, &stx, CEPH_STATX_INO, 0)); + vinodeno_t snapdir_vino(stx.stx_ino, stx.stx_dev); + + ASSERT_EQ(0, ceph_statx(cmount, snap_path, &stx, CEPH_STATX_INO, 0)); + vinodeno_t snap_vino(stx.stx_ino, stx.stx_dev); + + ASSERT_EQ(0, ceph_statx(cmount, snap_file, &stx, CEPH_STATX_INO, 0)); + vinodeno_t snap_file_vino(stx.stx_ino, stx.stx_dev); + + // Remount + ASSERT_EQ(0, ceph_unmount(cmount)); + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, NULL)); + + // Find them all + Inode *inode; + ASSERT_EQ(0, ceph_ll_lookup_vino(cmount, dir_vino, &inode)); + ceph_ll_put(cmount, inode); + ASSERT_EQ(0, ceph_ll_lookup_vino(cmount, file_vino, &inode)); + ceph_ll_put(cmount, inode); + ASSERT_EQ(0, ceph_ll_lookup_vino(cmount, snapdir_vino, &inode)); + ceph_ll_put(cmount, inode); + ASSERT_EQ(0, ceph_ll_lookup_vino(cmount, snap_vino, &inode)); + ceph_ll_put(cmount, inode); + ASSERT_EQ(0, ceph_ll_lookup_vino(cmount, snap_file_vino, &inode)); + ceph_ll_put(cmount, inode); + + // cleanup + ASSERT_EQ(0, ceph_rmsnap(cmount, dir_path, snap_name)); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Openat) { + pid_t mypid = getpid(); + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + char c_rel_dir[64]; + char c_dir[128]; + sprintf(c_rel_dir, "open_test_%d", mypid); + sprintf(c_dir, "/%s", c_rel_dir); + ASSERT_EQ(0, ceph_mkdir(cmount, c_dir, 0777)); + + int root_fd = ceph_open(cmount, "/", O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, root_fd); + + int dir_fd = ceph_openat(cmount, root_fd, c_rel_dir, O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, dir_fd); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statxat(cmount, root_fd, c_rel_dir, &stx, 0, 0), 0); + ASSERT_EQ(stx.stx_mode & S_IFMT, S_IFDIR); + + char c_rel_path[64]; + char c_path[256]; + sprintf(c_rel_path, "created_file_%d", mypid); + sprintf(c_path, "%s/created_file_%d", c_dir, mypid); + int file_fd = ceph_openat(cmount, dir_fd, c_rel_path, O_RDONLY | O_CREAT, 0666); + ASSERT_LE(0, file_fd); + + ASSERT_EQ(ceph_statxat(cmount, dir_fd, c_rel_path, &stx, 0, 0), 0); + ASSERT_EQ(stx.stx_mode & S_IFMT, S_IFREG); + + ASSERT_EQ(0, ceph_close(cmount, file_fd)); + ASSERT_EQ(0, ceph_close(cmount, dir_fd)); + ASSERT_EQ(0, ceph_close(cmount, root_fd)); + + ASSERT_EQ(0, ceph_unlink(cmount, c_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, c_dir)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Statxat) { + 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 dir_name[64]; + char rel_file_name_1[128]; + char rel_file_name_2[256]; + + char dir_path[512]; + char file_path[1024]; + + // relative paths for *at() calls + sprintf(dir_name, "dir0_%d", getpid()); + sprintf(rel_file_name_1, "file_%d", getpid()); + sprintf(rel_file_name_2, "%s/%s", dir_name, rel_file_name_1); + + sprintf(dir_path, "/%s", dir_name); + sprintf(file_path, "%s/%s", dir_path, rel_file_name_1); + + ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755)); + int fd = ceph_open(cmount, file_path, O_WRONLY|O_CREAT, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + struct ceph_statx stx; + + // test relative to root + fd = ceph_open(cmount, "/", O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(ceph_statxat(cmount, fd, dir_name, &stx, 0, 0), 0); + ASSERT_EQ(stx.stx_mode & S_IFMT, S_IFDIR); + ASSERT_EQ(ceph_statxat(cmount, fd, rel_file_name_2, &stx, 0, 0), 0); + ASSERT_EQ(stx.stx_mode & S_IFMT, S_IFREG); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + // test relative to dir + fd = ceph_open(cmount, dir_path, O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(ceph_statxat(cmount, fd, rel_file_name_1, &stx, 0, 0), 0); + ASSERT_EQ(stx.stx_mode & S_IFMT, S_IFREG); + + // delete the dirtree, recreate and verify + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755)); + int fd1 = ceph_open(cmount, file_path, O_WRONLY|O_CREAT, 0666); + ASSERT_LE(0, fd1); + ASSERT_EQ(0, ceph_close(cmount, fd1)); + ASSERT_EQ(ceph_statxat(cmount, fd, rel_file_name_1, &stx, 0, 0), -CEPHFS_ENOENT); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, StatxatATFDCWD) { + 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 dir_name[64]; + char rel_file_name_1[128]; + + char dir_path[512]; + char file_path[1024]; + + // relative paths for *at() calls + sprintf(dir_name, "dir0_%d", getpid()); + sprintf(rel_file_name_1, "file_%d", getpid()); + + sprintf(dir_path, "/%s", dir_name); + sprintf(file_path, "%s/%s", dir_path, rel_file_name_1); + + ASSERT_EQ(0, ceph_mkdir(cmount, dir_path, 0755)); + int fd = ceph_open(cmount, file_path, O_WRONLY|O_CREAT, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + struct ceph_statx stx; + // chdir and test with CEPHFS_AT_FDCWD + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + ASSERT_EQ(ceph_statxat(cmount, CEPHFS_AT_FDCWD, rel_file_name_1, &stx, 0, 0), 0); + ASSERT_EQ(stx.stx_mode & S_IFMT, S_IFREG); + + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Fdopendir) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char foostr[256]; + sprintf(foostr, "/dir_ls%d", mypid); + ASSERT_EQ(ceph_mkdir(cmount, foostr, 0777), 0); + + char bazstr[512]; + sprintf(bazstr, "%s/elif", foostr); + int fd = ceph_open(cmount, bazstr, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, foostr, O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + struct ceph_dir_result *ls_dir = NULL; + ASSERT_EQ(ceph_fdopendir(cmount, fd, &ls_dir), 0); + + // not guaranteed to get . and .. first, but its a safe assumption in this case + struct dirent *result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "elif"); + + ASSERT_TRUE(ceph_readdir(cmount, ls_dir) == NULL); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_closedir(cmount, ls_dir)); + ASSERT_EQ(0, ceph_unlink(cmount, bazstr)); + ASSERT_EQ(0, ceph_rmdir(cmount, foostr)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, FdopendirATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char foostr[256]; + sprintf(foostr, "/dir_ls%d", mypid); + ASSERT_EQ(ceph_mkdir(cmount, foostr, 0777), 0); + + char bazstr[512]; + sprintf(bazstr, "%s/elif", foostr); + int fd = ceph_open(cmount, bazstr, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ASSERT_EQ(0, ceph_chdir(cmount, foostr)); + struct ceph_dir_result *ls_dir = NULL; + ASSERT_EQ(ceph_fdopendir(cmount, CEPHFS_AT_FDCWD, &ls_dir), 0); + + // not guaranteed to get . and .. first, but its a safe assumption in this case + struct dirent *result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "elif"); + + ASSERT_TRUE(ceph_readdir(cmount, ls_dir) == NULL); + + ASSERT_EQ(0, ceph_closedir(cmount, ls_dir)); + ASSERT_EQ(0, ceph_unlink(cmount, bazstr)); + ASSERT_EQ(0, ceph_rmdir(cmount, foostr)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, FdopendirReaddirTestWithDelete) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char foostr[256]; + sprintf(foostr, "/dir_ls%d", mypid); + ASSERT_EQ(ceph_mkdir(cmount, foostr, 0777), 0); + + char bazstr[512]; + sprintf(bazstr, "%s/elif", foostr); + int fd = ceph_open(cmount, bazstr, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, foostr, O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + struct ceph_dir_result *ls_dir = NULL; + ASSERT_EQ(ceph_fdopendir(cmount, fd, &ls_dir), 0); + + ASSERT_EQ(0, ceph_unlink(cmount, bazstr)); + ASSERT_EQ(0, ceph_rmdir(cmount, foostr)); + + // not guaranteed to get . and .. first, but its a safe assumption + // in this case. also, note that we may or may not get other + // entries. + struct dirent *result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, "."); + result = ceph_readdir(cmount, ls_dir); + ASSERT_TRUE(result != NULL); + ASSERT_STREQ(result->d_name, ".."); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_closedir(cmount, ls_dir)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, FdopendirOnNonDir) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char foostr[256]; + sprintf(foostr, "/dir_ls%d", mypid); + ASSERT_EQ(ceph_mkdir(cmount, foostr, 0777), 0); + + char bazstr[512]; + sprintf(bazstr, "%s/file", foostr); + int fd = ceph_open(cmount, bazstr, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + + struct ceph_dir_result *ls_dir = NULL; + ASSERT_EQ(ceph_fdopendir(cmount, fd, &ls_dir), -CEPHFS_ENOTDIR); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ASSERT_EQ(0, ceph_unlink(cmount, bazstr)); + ASSERT_EQ(0, ceph_rmdir(cmount, foostr)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Mkdirat) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path1[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path1, "/%s", dir_name); + + char dir_path2[512]; + char rel_dir_path2[512]; + sprintf(dir_path2, "%s/dir_%d", dir_path1, mypid); + sprintf(rel_dir_path2, "%s/dir_%d", dir_name, mypid); + + int fd = ceph_open(cmount, "/", O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + + ASSERT_EQ(0, ceph_mkdirat(cmount, fd, dir_name, 0777)); + ASSERT_EQ(0, ceph_mkdirat(cmount, fd, rel_dir_path2, 0666)); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path2)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path1)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, MkdiratATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path1[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path1, "/%s", dir_name); + + char dir_path2[512]; + sprintf(dir_path2, "%s/dir_%d", dir_path1, mypid); + + ASSERT_EQ(0, ceph_mkdirat(cmount, CEPHFS_AT_FDCWD, dir_name, 0777)); + + ASSERT_EQ(0, ceph_chdir(cmount, dir_path1)); + ASSERT_EQ(0, ceph_mkdirat(cmount, CEPHFS_AT_FDCWD, dir_name, 0666)); + + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path2)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path1)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Readlinkat) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512]; + sprintf(rel_file_path, "%s/elif", dir_name); + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + char link_path[128]; + char rel_link_path[64]; + sprintf(rel_link_path, "linkfile_%d", mypid); + sprintf(link_path, "/%s", rel_link_path); + ASSERT_EQ(0, ceph_symlink(cmount, rel_file_path, link_path)); + + fd = ceph_open(cmount, "/", O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + size_t target_len = strlen(rel_file_path); + char target[target_len+1]; + ASSERT_EQ(target_len, ceph_readlinkat(cmount, fd, rel_link_path, target, target_len)); + target[target_len] = '\0'; + ASSERT_EQ(0, memcmp(target, rel_file_path, target_len)); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, link_path)); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, ReadlinkatATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "./elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + char link_path[PATH_MAX]; + char rel_link_path[1024]; + sprintf(rel_link_path, "./linkfile_%d", mypid); + sprintf(link_path, "%s/%s", dir_path, rel_link_path); + ASSERT_EQ(0, ceph_symlink(cmount, rel_file_path, link_path)); + + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + size_t target_len = strlen(rel_file_path); + char target[target_len+1]; + ASSERT_EQ(target_len, ceph_readlinkat(cmount, CEPHFS_AT_FDCWD, rel_link_path, target, target_len)); + target[target_len] = '\0'; + ASSERT_EQ(0, memcmp(target, rel_file_path, target_len)); + + ASSERT_EQ(0, ceph_unlink(cmount, link_path)); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Symlinkat) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512]; + sprintf(rel_file_path, "%s/elif", dir_name); + sprintf(file_path, "%s/elif", dir_path); + + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + char link_path[128]; + char rel_link_path[64]; + sprintf(rel_link_path, "linkfile_%d", mypid); + sprintf(link_path, "/%s", rel_link_path); + + fd = ceph_open(cmount, "/", O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_symlinkat(cmount, rel_file_path, fd, rel_link_path)); + + size_t target_len = strlen(rel_file_path); + char target[target_len+1]; + ASSERT_EQ(target_len, ceph_readlinkat(cmount, fd, rel_link_path, target, target_len)); + target[target_len] = '\0'; + ASSERT_EQ(0, memcmp(target, rel_file_path, target_len)); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unlink(cmount, link_path)); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SymlinkatATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "./elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + char link_path[PATH_MAX]; + char rel_link_path[1024]; + sprintf(rel_link_path, "./linkfile_%d", mypid); + sprintf(link_path, "%s/%s", dir_path, rel_link_path); + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + ASSERT_EQ(0, ceph_symlinkat(cmount, rel_file_path, CEPHFS_AT_FDCWD, rel_link_path)); + + size_t target_len = strlen(rel_file_path); + char target[target_len+1]; + ASSERT_EQ(target_len, ceph_readlinkat(cmount, CEPHFS_AT_FDCWD, rel_link_path, target, target_len)); + target[target_len] = '\0'; + ASSERT_EQ(0, memcmp(target, rel_file_path, target_len)); + + ASSERT_EQ(0, ceph_unlink(cmount, link_path)); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Unlinkat) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, dir_path, O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + ASSERT_EQ(-CEPHFS_ENOTDIR, ceph_unlinkat(cmount, fd, rel_file_path, AT_REMOVEDIR)); + ASSERT_EQ(0, ceph_unlinkat(cmount, fd, rel_file_path, 0)); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, "/", O_DIRECTORY | O_RDONLY, 0); + ASSERT_EQ(-CEPHFS_EISDIR, ceph_unlinkat(cmount, fd, dir_name, 0)); + ASSERT_EQ(0, ceph_unlinkat(cmount, fd, dir_name, AT_REMOVEDIR)); + ASSERT_LE(0, fd); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, UnlinkatATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDONLY, 0666); + ASSERT_LE(0, fd); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + ASSERT_EQ(-CEPHFS_ENOTDIR, ceph_unlinkat(cmount, CEPHFS_AT_FDCWD, rel_file_path, AT_REMOVEDIR)); + ASSERT_EQ(0, ceph_unlinkat(cmount, CEPHFS_AT_FDCWD, rel_file_path, 0)); + + ASSERT_EQ(0, ceph_chdir(cmount, "/")); + ASSERT_EQ(-CEPHFS_EISDIR, ceph_unlinkat(cmount, CEPHFS_AT_FDCWD, dir_name, 0)); + ASSERT_EQ(0, ceph_unlinkat(cmount, CEPHFS_AT_FDCWD, dir_name, AT_REMOVEDIR)); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Chownat) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDWR, 0666); + ASSERT_LE(0, fd); + + // set perms to readable and writeable only by owner + ASSERT_EQ(ceph_fchmod(cmount, fd, 0600), 0); + ceph_close(cmount, fd); + + fd = ceph_open(cmount, dir_path, O_DIRECTORY | O_RDONLY, 0); + // change ownership to nobody -- we assume nobody exists and id is always 65534 + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "0"), 0); + ASSERT_EQ(ceph_chownat(cmount, fd, rel_file_path, 65534, 65534, 0), 0); + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "1"), 0); + ceph_close(cmount, fd); + + // "nobody" will be ignored on Windows + #ifndef _WIN32 + fd = ceph_open(cmount, file_path, O_RDWR, 0); + ASSERT_EQ(fd, -CEPHFS_EACCES); + #endif + + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "0"), 0); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "1"), 0); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, ChownatATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDWR, 0666); + ASSERT_LE(0, fd); + + // set perms to readable and writeable only by owner + ASSERT_EQ(ceph_fchmod(cmount, fd, 0600), 0); + ceph_close(cmount, fd); + + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + // change ownership to nobody -- we assume nobody exists and id is always 65534 + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "0"), 0); + ASSERT_EQ(ceph_chownat(cmount, CEPHFS_AT_FDCWD, rel_file_path, 65534, 65534, 0), 0); + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "1"), 0); + + // "nobody" will be ignored on Windows + #ifndef _WIN32 + fd = ceph_open(cmount, file_path, O_RDWR, 0); + ASSERT_EQ(fd, -CEPHFS_EACCES); + #endif + + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "0"), 0); + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(ceph_conf_set(cmount, "client_permissions", "1"), 0); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Chmodat) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDWR, 0666); + ASSERT_LE(0, fd); + const char *bytes = "foobarbaz"; + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + fd = ceph_open(cmount, dir_path, O_DIRECTORY | O_RDONLY, 0); + + // set perms to read but can't write + ASSERT_EQ(ceph_chmodat(cmount, fd, rel_file_path, 0400, 0), 0); + ASSERT_EQ(ceph_open(cmount, file_path, O_RDWR, 0), -CEPHFS_EACCES); + + // reset back to writeable + ASSERT_EQ(ceph_chmodat(cmount, fd, rel_file_path, 0600, 0), 0); + int fd2 = ceph_open(cmount, file_path, O_RDWR, 0); + ASSERT_LE(0, fd2); + + ASSERT_EQ(0, ceph_close(cmount, fd2)); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, ChmodatATFDCWD) { + pid_t mypid = getpid(); + + 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, "/"), 0); + + char dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDWR, 0666); + ASSERT_LE(0, fd); + const char *bytes = "foobarbaz"; + ASSERT_EQ(ceph_write(cmount, fd, bytes, strlen(bytes), 0), (int)strlen(bytes)); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + // set perms to read but can't write + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + ASSERT_EQ(ceph_chmodat(cmount, CEPHFS_AT_FDCWD, rel_file_path, 0400, 0), 0); + ASSERT_EQ(ceph_open(cmount, file_path, O_RDWR, 0), -CEPHFS_EACCES); + + // reset back to writeable + ASSERT_EQ(ceph_chmodat(cmount, CEPHFS_AT_FDCWD, rel_file_path, 0600, 0), 0); + int fd2 = ceph_open(cmount, file_path, O_RDWR, 0); + ASSERT_LE(0, fd2); + ASSERT_EQ(0, ceph_close(cmount, fd2)); + + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, Utimensat) { + pid_t mypid = getpid(); + + 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 dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDWR, 0666); + ASSERT_LE(0, fd); + + struct timespec times[2]; + get_current_time_timespec(times); + + fd = ceph_open(cmount, dir_path, O_DIRECTORY | O_RDONLY, 0); + ASSERT_LE(0, fd); + EXPECT_EQ(0, ceph_utimensat(cmount, fd, rel_file_path, times, 0)); + ceph_close(cmount, fd); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, file_path, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(times[0])); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(times[1])); + + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, UtimensatATFDCWD) { + pid_t mypid = getpid(); + + 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 dir_name[128]; + char dir_path[256]; + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + char file_path[512]; + char rel_file_path[512] = "elif"; + sprintf(file_path, "%s/elif", dir_path); + int fd = ceph_open(cmount, file_path, O_CREAT|O_RDWR, 0666); + ASSERT_LE(0, fd); + + struct timespec times[2]; + get_current_time_timespec(times); + + ASSERT_EQ(0, ceph_chdir(cmount, dir_path)); + EXPECT_EQ(0, ceph_utimensat(cmount, CEPHFS_AT_FDCWD, rel_file_path, times, 0)); + + struct ceph_statx stx; + ASSERT_EQ(ceph_statx(cmount, file_path, &stx, + CEPH_STATX_MTIME|CEPH_STATX_ATIME, 0), 0); + ASSERT_EQ(utime_t(stx.stx_atime), utime_t(times[0])); + ASSERT_EQ(utime_t(stx.stx_mtime), utime_t(times[1])); + + ASSERT_EQ(0, ceph_unlink(cmount, file_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LookupMdsPrivateInos) { + 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); + + Inode *inode; + for (int ino = 0; ino < MDS_INO_SYSTEM_BASE; ino++) { + if (MDS_IS_PRIVATE_INO(ino)) { + ASSERT_EQ(-CEPHFS_ESTALE, ceph_ll_lookup_inode(cmount, ino, &inode)); + } else if (ino == CEPH_INO_ROOT || ino == CEPH_INO_GLOBAL_SNAPREALM) { + ASSERT_EQ(0, ceph_ll_lookup_inode(cmount, ino, &inode)); + ceph_ll_put(cmount, inode); + } else if (ino == CEPH_INO_LOST_AND_FOUND) { + // the ino 3 will only exists after the recovery tool ran, so + // it may return -CEPHFS_ESTALE with a fresh fs cluster + int r = ceph_ll_lookup_inode(cmount, ino, &inode); + if (r == 0) { + ceph_ll_put(cmount, inode); + } else { + ASSERT_TRUE(r == -CEPHFS_ESTALE); + } + } else { + // currently the ino 0 and 4~99 is not useded yet. + ASSERT_EQ(-CEPHFS_ESTALE, ceph_ll_lookup_inode(cmount, ino, &inode)); + } + } + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SetMountTimeoutPostMount) { + 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); + + ASSERT_EQ(-CEPHFS_EINVAL, ceph_set_mount_timeout(cmount, 5)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SetMountTimeout) { + 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(0, ceph_set_mount_timeout(cmount, 5)); + ASSERT_EQ(ceph_mount(cmount, NULL), 0); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, FsCrypt) { + 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_xattr_file[NAME_MAX]; + sprintf(test_xattr_file, "test_fscrypt_%d", getpid()); + int fd = ceph_open(cmount, test_xattr_file, O_RDWR|O_CREAT, 0666); + ASSERT_GT(fd, 0); + + ASSERT_EQ(0, ceph_fsetxattr(cmount, fd, "ceph.fscrypt.auth", "foo", 3, CEPH_XATTR_CREATE)); + ASSERT_EQ(0, ceph_fsetxattr(cmount, fd, "ceph.fscrypt.file", "foo", 3, CEPH_XATTR_CREATE)); + + char buf[64]; + ASSERT_EQ(3, ceph_fgetxattr(cmount, fd, "ceph.fscrypt.auth", buf, sizeof(buf))); + ASSERT_EQ(3, ceph_fgetxattr(cmount, fd, "ceph.fscrypt.file", buf, sizeof(buf))); + ASSERT_EQ(0, ceph_close(cmount, fd)); + + ASSERT_EQ(0, ceph_unmount(cmount)); + ASSERT_EQ(0, ceph_mount(cmount, NULL)); + + fd = ceph_open(cmount, test_xattr_file, O_RDWR, 0666); + ASSERT_GT(fd, 0); + ASSERT_EQ(3, ceph_fgetxattr(cmount, fd, "ceph.fscrypt.auth", buf, sizeof(buf))); + ASSERT_EQ(3, ceph_fgetxattr(cmount, fd, "ceph.fscrypt.file", buf, sizeof(buf))); + + ASSERT_EQ(0, ceph_close(cmount, fd)); + ASSERT_EQ(0, ceph_unmount(cmount)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SnapdirAttrs) { + 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 dir_name[128]; + char dir_path[256]; + char snap_dir_path[512]; + + pid_t mypid = getpid(); + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + sprintf(snap_dir_path, "%s/.snap", dir_path); + + Inode *dir, *root; + struct ceph_statx stx_dir; + struct ceph_statx stx_snap_dir; + struct ceph_statx stx_root_snap_dir; + UserPerm *perms = ceph_mount_perms(cmount); + + ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); + ASSERT_EQ(ceph_ll_mkdir(cmount, root, dir_name, 0755, &dir, &stx_dir, 0, 0, perms), 0); + + ASSERT_EQ(ceph_statx(cmount, dir_path, &stx_dir, + CEPH_STATX_MTIME|CEPH_STATX_ATIME|CEPH_STATX_MODE|CEPH_STATX_MODE|CEPH_STATX_GID|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir, + CEPH_STATX_MTIME|CEPH_STATX_ATIME|CEPH_STATX_MODE|CEPH_STATX_MODE|CEPH_STATX_GID|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, "/.snap", &stx_root_snap_dir, + CEPH_STATX_MTIME|CEPH_STATX_ATIME|CEPH_STATX_MODE|CEPH_STATX_MODE|CEPH_STATX_GID|CEPH_STATX_VERSION, 0), 0); + + // these should match the parent directories attrs + ASSERT_EQ(stx_dir.stx_mode, stx_snap_dir.stx_mode); + ASSERT_EQ(stx_dir.stx_uid, stx_snap_dir.stx_uid); + ASSERT_EQ(stx_dir.stx_gid, stx_snap_dir.stx_gid); + ASSERT_EQ(utime_t(stx_dir.stx_atime), utime_t(stx_snap_dir.stx_atime)); + // these should match the closest snaprealm ancestor (root in this + // case) attrs + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir.stx_mtime)); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir.stx_ctime)); + ASSERT_EQ(stx_root_snap_dir.stx_version, stx_snap_dir.stx_version); + + // chown -- for this we need to be "root" + UserPerm *rootcred = ceph_userperm_new(0, 0, 0, NULL); + ASSERT_TRUE(rootcred); + stx_dir.stx_uid++; + stx_dir.stx_gid++; + ASSERT_EQ(ceph_ll_setattr(cmount, dir, &stx_dir, CEPH_SETATTR_UID|CEPH_SETATTR_GID, rootcred), 0); + + memset(&stx_dir, 0, sizeof(stx_dir)); + memset(&stx_snap_dir, 0, sizeof(stx_snap_dir)); + + ASSERT_EQ(ceph_statx(cmount, dir_path, &stx_dir, + CEPH_STATX_MTIME|CEPH_STATX_ATIME|CEPH_STATX_MODE|CEPH_STATX_MODE|CEPH_STATX_GID|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir, + CEPH_STATX_MTIME|CEPH_STATX_ATIME|CEPH_STATX_MODE|CEPH_STATX_MODE|CEPH_STATX_GID|CEPH_STATX_VERSION, 0), 0); + + ASSERT_EQ(stx_dir.stx_mode, stx_snap_dir.stx_mode); + ASSERT_EQ(stx_dir.stx_uid, stx_snap_dir.stx_uid); + ASSERT_EQ(stx_dir.stx_gid, stx_snap_dir.stx_gid); + ASSERT_EQ(utime_t(stx_dir.stx_atime), utime_t(stx_snap_dir.stx_atime)); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir.stx_mtime)); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir.stx_ctime)); + ASSERT_EQ(stx_root_snap_dir.stx_version, stx_snap_dir.stx_version); + + ASSERT_EQ(ceph_ll_rmdir(cmount, root, dir_name, rootcred), 0); + ASSERT_EQ(0, ceph_unmount(cmount)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SnapdirAttrsOnSnapCreate) { + 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 dir_name[128]; + char dir_path[256]; + char snap_dir_path[512]; + + pid_t mypid = getpid(); + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + sprintf(snap_dir_path, "%s/.snap", dir_path); + + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + struct ceph_statx stx_dir; + struct ceph_statx stx_snap_dir; + struct ceph_statx stx_root_snap_dir; + ASSERT_EQ(ceph_statx(cmount, dir_path, &stx_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, "/.snap", &stx_root_snap_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir.stx_mtime)); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir.stx_ctime)); + ASSERT_EQ(stx_root_snap_dir.stx_version, stx_snap_dir.stx_version); + + char snap_path[1024]; + sprintf(snap_path, "%s/snap_a", snap_dir_path); + ASSERT_EQ(ceph_mkdir(cmount, snap_path, 0777), 0); + + struct ceph_statx stx_snap_dir_1; + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_1, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_LT(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir_1.stx_mtime)); + ASSERT_LT(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir_1.stx_ctime)); + ASSERT_LT(stx_root_snap_dir.stx_version, stx_snap_dir_1.stx_version); + + ASSERT_EQ(0, ceph_rmdir(cmount, snap_path)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ASSERT_EQ(0, ceph_unmount(cmount)); + ceph_shutdown(cmount); +} + + +TEST(LibCephFS, SnapdirAttrsOnSnapDelete) { + 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 dir_name[128]; + char dir_path[256]; + char snap_dir_path[512]; + + pid_t mypid = getpid(); + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + sprintf(snap_dir_path, "%s/.snap", dir_path); + + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + struct ceph_statx stx_dir; + struct ceph_statx stx_snap_dir; + struct ceph_statx stx_root_snap_dir; + ASSERT_EQ(ceph_statx(cmount, dir_path, &stx_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, "/.snap", &stx_root_snap_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir.stx_mtime)); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir.stx_mtime)); + ASSERT_EQ(stx_root_snap_dir.stx_version, stx_snap_dir.stx_version); + + char snap_path[1024]; + sprintf(snap_path, "%s/snap_a", snap_dir_path); + ASSERT_EQ(ceph_mkdir(cmount, snap_path, 0777), 0); + + struct ceph_statx stx_snap_dir_1; + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_1, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_LT(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir_1.stx_mtime)); + ASSERT_LT(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir_1.stx_ctime)); + ASSERT_LT(stx_root_snap_dir.stx_version, stx_snap_dir_1.stx_version); + + ASSERT_EQ(0, ceph_rmdir(cmount, snap_path)); + + struct ceph_statx stx_snap_dir_2; + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_2, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_LT(utime_t(stx_snap_dir_1.stx_mtime), utime_t(stx_snap_dir_2.stx_mtime)); + ASSERT_LT(utime_t(stx_snap_dir_1.stx_ctime), utime_t(stx_snap_dir_2.stx_ctime)); + ASSERT_LT(stx_snap_dir_1.stx_version, stx_snap_dir_2.stx_version); + + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ASSERT_EQ(0, ceph_unmount(cmount)); + ceph_shutdown(cmount); +} + +TEST(LibCephFS, SnapdirAttrsOnSnapRename) { + 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 dir_name[128]; + char dir_path[256]; + char snap_dir_path[512]; + + pid_t mypid = getpid(); + sprintf(dir_name, "dir_%d", mypid); + sprintf(dir_path, "/%s", dir_name); + sprintf(snap_dir_path, "%s/.snap", dir_path); + + ASSERT_EQ(ceph_mkdir(cmount, dir_path, 0777), 0); + + struct ceph_statx stx_dir; + struct ceph_statx stx_snap_dir; + struct ceph_statx stx_root_snap_dir; + ASSERT_EQ(ceph_statx(cmount, dir_path, &stx_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(ceph_statx(cmount, "/.snap", &stx_root_snap_dir, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir.stx_mtime)); + ASSERT_EQ(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir.stx_ctime)); + ASSERT_EQ(stx_root_snap_dir.stx_version, stx_snap_dir.stx_version); + + char snap_path[1024]; + sprintf(snap_path, "%s/snap_a", snap_dir_path); + ASSERT_EQ(ceph_mkdir(cmount, snap_path, 0777), 0); + + struct ceph_statx stx_snap_dir_1; + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_1, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_LT(utime_t(stx_root_snap_dir.stx_mtime), utime_t(stx_snap_dir_1.stx_mtime)); + ASSERT_LT(utime_t(stx_root_snap_dir.stx_ctime), utime_t(stx_snap_dir_1.stx_ctime)); + ASSERT_LT(stx_root_snap_dir.stx_version, stx_snap_dir_1.stx_version); + + char snap_path_r[1024]; + sprintf(snap_path_r, "%s/snap_b", snap_dir_path); + ASSERT_EQ(ceph_rename(cmount, snap_path, snap_path_r), 0); + + struct ceph_statx stx_snap_dir_2; + ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_2, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + ASSERT_LT(utime_t(stx_snap_dir_1.stx_mtime), utime_t(stx_snap_dir_2.stx_mtime)); + ASSERT_LT(utime_t(stx_snap_dir_1.stx_ctime), utime_t(stx_snap_dir_2.stx_ctime)); + ASSERT_LT(stx_snap_dir_1.stx_version, stx_snap_dir_2.stx_version); + + ASSERT_EQ(0, ceph_rmdir(cmount, snap_path_r)); + ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); + ASSERT_EQ(0, ceph_unmount(cmount)); + ceph_shutdown(cmount); +} diff --git a/src/test/libcephfs/vxattr.cc b/src/test/libcephfs/vxattr.cc new file mode 100644 index 000000000..4d9eaf5e4 --- /dev/null +++ b/src/test/libcephfs/vxattr.cc @@ -0,0 +1,385 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2021 Red Hat Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/compat.h" +#include "gtest/gtest.h" +#include "include/cephfs/libcephfs.h" +#include "include/fs_types.h" +#include "mds/mdstypes.h" +#include "include/stat.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/uio.h> +#include <sys/time.h> +#include <string.h> + +#ifndef _WIN32 +#include <sys/resource.h> +#endif + +#include "common/Clock.h" +#include "common/ceph_json.h" + +#ifdef __linux__ +#include <limits.h> +#include <sys/xattr.h> +#endif + +#include <fmt/format.h> +#include <map> +#include <vector> +#include <thread> +#include <regex> +#include <string> + +using namespace std; + +TEST(LibCephFS, LayoutVerifyDefaultLayout) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d0/subdir", 0777)); + + { + char value[1024] = ""; + int r = 0; + + // check for default layout + r = ceph_getxattr(cmount, "/", "ceph.dir.layout.json", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + std::clog << "layout:" << value << std::endl; + ASSERT_STRNE((char*)NULL, strstr(value, "\"inheritance\": \"@default\"")); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0/subdir")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LayoutSetAndVerifyNewAndInheritedLayout) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d0/subdir", 0777)); + + std::string pool_name_set; + + { + char value[1024] = ""; + int r = 0; + + r = ceph_getxattr(cmount, "/", "ceph.dir.layout.json", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + + JSONParser json_parser; + ASSERT_EQ(json_parser.parse(value, r), 1); + ASSERT_EQ(json_parser.is_object(), 1); + + std::string pool_name; + + JSONDecoder::decode_json("pool_name", pool_name, &json_parser, true); + + pool_name_set = pool_name; + + // set a new layout + std::string new_layout; + new_layout += "{"; + new_layout += "\"stripe_unit\": 65536, "; + new_layout += "\"stripe_count\": 1, "; + new_layout += "\"object_size\": 65536, "; + new_layout += "\"pool_name\": \"" + pool_name + "\""; + new_layout += "}"; + + ASSERT_EQ(0, ceph_setxattr(cmount, "test/d0", "ceph.dir.layout.json", (void*)new_layout.c_str(), new_layout.length(), XATTR_CREATE)); + } + + { + char value[1024] = ""; + int r = 0; + + r = ceph_getxattr(cmount, "test/d0", "ceph.dir.layout.json", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + std::clog << "layout:" << value << std::endl; + + JSONParser json_parser; + ASSERT_EQ(json_parser.parse(value, r), 1); + ASSERT_EQ(json_parser.is_object(), 1); + + int64_t object_size; + int64_t stripe_unit; + int64_t stripe_count; + std::string pool_name; + std::string inheritance; + + JSONDecoder::decode_json("pool_name", pool_name, &json_parser, true); + JSONDecoder::decode_json("object_size", object_size, &json_parser, true); + JSONDecoder::decode_json("stripe_unit", stripe_unit, &json_parser, true); + JSONDecoder::decode_json("stripe_count", stripe_count, &json_parser, true); + JSONDecoder::decode_json("inheritance", inheritance, &json_parser, true); + + // now verify the layout + ASSERT_EQ(pool_name.compare(pool_name_set), 0); + ASSERT_EQ(object_size, 65536); + ASSERT_EQ(stripe_unit, 65536); + ASSERT_EQ(stripe_count, 1); + ASSERT_EQ(inheritance.compare("@set"), 0); + } + + { + char value[1024] = ""; + int r = 0; + + JSONParser json_parser; + std::string inheritance; + + // now check that the subdir layout is inherited + r = ceph_getxattr(cmount, "test/d0/subdir", "ceph.dir.layout.json", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + std::clog << "layout:" << value << std::endl; + ASSERT_EQ(json_parser.parse(value, r), 1); + ASSERT_EQ(json_parser.is_object(), 1); + JSONDecoder::decode_json("inheritance", inheritance, &json_parser, true); + ASSERT_EQ(inheritance.compare("@inherited"), 0); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0/subdir")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LayoutSetBadJSON) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d0/subdir", 0777)); + + { + // set a new layout and verify the same + const char *new_layout = "" // bad json without starting brace + "\"stripe_unit\": 65536, " + "\"stripe_count\": 1, " + "\"object_size\": 65536, " + "\"pool_name\": \"cephfs.a.data\", " + "}"; + // try to set a malformed JSON, eg. without an open brace + ASSERT_EQ(-CEPHFS_EINVAL, ceph_setxattr(cmount, "test/d0", "ceph.dir.layout.json", (void*)new_layout, strlen(new_layout), XATTR_CREATE)); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0/subdir")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LayoutSetBadPoolName) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d0/subdir", 0777)); + + { + // try setting a bad pool name + ASSERT_EQ(-CEPHFS_EINVAL, ceph_setxattr(cmount, "test/d0", "ceph.dir.layout.pool_name", (void*)"UglyPoolName", 12, XATTR_CREATE)); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0/subdir")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LayoutSetBadPoolId) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d0/subdir", 0777)); + + { + // try setting a bad pool id + ASSERT_EQ(-CEPHFS_EINVAL, ceph_setxattr(cmount, "test/d0", "ceph.dir.layout.pool_id", (void*)"300", 3, XATTR_CREATE)); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0/subdir")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, LayoutSetInvalidFieldName) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d0/subdir", 0777)); + + { + // try to set in invalid field + ASSERT_EQ(-CEPHFS_ENODATA, ceph_setxattr(cmount, "test/d0", "ceph.dir.layout.bad_field", (void*)"300", 3, XATTR_CREATE)); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0/subdir")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d0")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetAndSetDirPin) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d1", 0777)); + + { + char value[1024] = ""; + int r = ceph_getxattr(cmount, "test/d1", "ceph.dir.pin", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + ASSERT_STREQ("-1", value); + } + + { + char value[1024] = ""; + int r = -1; + + ASSERT_EQ(0, ceph_setxattr(cmount, "test/d1", "ceph.dir.pin", (void*)"1", 1, XATTR_CREATE)); + + r = ceph_getxattr(cmount, "test/d1", "ceph.dir.pin", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + ASSERT_STREQ("1", value); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d1")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetAndSetDirDistribution) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d2", 0777)); + + { + char value[1024] = ""; + int r = ceph_getxattr(cmount, "test/d2", "ceph.dir.pin.distributed", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + ASSERT_STREQ("0", value); + } + + { + char value[1024] = ""; + int r = -1; + + ASSERT_EQ(0, ceph_setxattr(cmount, "test/d2", "ceph.dir.pin.distributed", (void*)"1", 1, XATTR_CREATE)); + + r = ceph_getxattr(cmount, "test/d2", "ceph.dir.pin.distributed", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + ASSERT_STREQ("1", value); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d2")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} + +TEST(LibCephFS, GetAndSetDirRandom) { + + struct ceph_mount_info *cmount; + ASSERT_EQ(0, ceph_create(&cmount, NULL)); + ASSERT_EQ(0, ceph_conf_read_file(cmount, NULL)); + ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); + ASSERT_EQ(0, ceph_mount(cmount, "/")); + + ASSERT_EQ(0, ceph_mkdirs(cmount, "test/d3", 0777)); + + { + char value[1024] = ""; + int r = ceph_getxattr(cmount, "test/d3", "ceph.dir.pin.random", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + ASSERT_STREQ("0", value); + } + + { + double val = (double)1.0/(double)128.0; + std::stringstream ss; + ss << val; + ASSERT_EQ(0, ceph_setxattr(cmount, "test/d3", "ceph.dir.pin.random", (void*)ss.str().c_str(), strlen(ss.str().c_str()), XATTR_CREATE)); + + char value[1024] = ""; + int r = -1; + + r = ceph_getxattr(cmount, "test/d3", "ceph.dir.pin.random", (void*)value, sizeof(value)); + ASSERT_GT(r, 0); + ASSERT_LT(r, sizeof value); + ASSERT_STREQ(ss.str().c_str(), value); + } + + ASSERT_EQ(0, ceph_rmdir(cmount, "test/d3")); + ASSERT_EQ(0, ceph_rmdir(cmount, "test")); + + ceph_shutdown(cmount); +} |