]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
test: tests for *at() libcephfs APIs 40810/head
authorVenky Shankar <vshankar@redhat.com>
Mon, 12 Apr 2021 09:26:03 +0000 (05:26 -0400)
committerVenky Shankar <vshankar@redhat.com>
Mon, 26 Apr 2021 13:14:27 +0000 (09:14 -0400)
Fixes: http://tracker.ceph.com/issues/50298
Signed-off-by: Venky Shankar <vshankar@redhat.com>
src/test/libcephfs/test.cc

index 769670f967b186ec3a79e23336c81019ff54b784..afbb2c7e23924922a09c237afd7beec1d31db8c4 100644 (file)
@@ -486,7 +486,7 @@ TEST(LibCephFS, DirLs) {
 
   // cleanup
   for(i = 0; i < r; ++i) {
-    sprintf(bazstr, "%s/dirf%d", foostr, i);
+    sprintf(bazstr, "dir_ls%d/dirf%d", mypid, i);
     ASSERT_EQ(0, ceph_unlink(cmount, bazstr));
   }
   ASSERT_EQ(0, ceph_rmdir(cmount, foostr));
@@ -2651,3 +2651,845 @@ TEST(LibCephFS, LookupVino) {
 
   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), -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), -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(-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(-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(-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(-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);
+
+  fd = ceph_open(cmount, file_path, O_RDWR, 0);
+  ASSERT_EQ(fd, -EACCES);
+
+  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);
+
+  fd = ceph_open(cmount, file_path, O_RDWR, 0);
+  ASSERT_EQ(fd, -EACCES);
+
+  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), -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), -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);
+}