|  | // Copyright 2021 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <fcntl.h> | 
|  | #include <lib/fit/defer.h> | 
|  | #include <limits.h> | 
|  | #include <sys/stat.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <fbl/unique_fd.h> | 
|  | #include <zxtest/zxtest.h> | 
|  |  | 
|  | #include "src/lib/fxl/strings/concatenate.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // An invalid file descriptor that is not equal to AT_FDCWD. | 
|  | constexpr int kInvalidFD = -1; | 
|  | static_assert(kInvalidFD != AT_FDCWD); | 
|  |  | 
|  | TEST(UnistdTest, TruncateWithNegativeLength) { | 
|  | const char* filename = "/tmp/truncate-with-negative-length-test"; | 
|  | fbl::unique_fd fd(open(filename, O_CREAT | O_RDWR, 0666)); | 
|  | ASSERT_TRUE(fd); | 
|  | EXPECT_EQ(-1, ftruncate(fd.get(), -1)); | 
|  | EXPECT_EQ(EINVAL, errno); | 
|  | EXPECT_EQ(-1, ftruncate(fd.get(), std::numeric_limits<off_t>::min())); | 
|  | EXPECT_EQ(EINVAL, errno); | 
|  |  | 
|  | EXPECT_EQ(-1, truncate(filename, -1)); | 
|  | EXPECT_EQ(EINVAL, errno); | 
|  | EXPECT_EQ(-1, truncate(filename, std::numeric_limits<off_t>::min())); | 
|  | EXPECT_EQ(EINVAL, errno); | 
|  | } | 
|  |  | 
|  | TEST(UnistdTest, LinkAt) { | 
|  | // Create a temporary directory, store its absolute path and chdir to it. | 
|  | char root_abs[] = "/tmp/fdio-linkat.XXXXXX"; | 
|  | ASSERT_NOT_NULL(mkdtemp(root_abs), "%s", strerror(errno)); | 
|  | auto cleanup_root = | 
|  | fit::defer([&root_abs]() { EXPECT_EQ(0, rmdir(root_abs), "%s", strerror(errno)); }); | 
|  | char prev_cwd[PATH_MAX]; | 
|  | ASSERT_NOT_NULL(getcwd(prev_cwd, sizeof(prev_cwd))); | 
|  | ASSERT_EQ(0, chdir(root_abs), "%s", strerror(errno)); | 
|  | auto restore_cwd = | 
|  | fit::defer([&prev_cwd]() { EXPECT_EQ(0, chdir(prev_cwd), "%s", strerror(errno)); }); | 
|  |  | 
|  | // Create a subdirectory with a file in it. | 
|  | constexpr char dir_name[] = "dir", foo_name[] = "foo", foo_rel[] = "dir/foo"; | 
|  | ASSERT_EQ(0, mkdir(dir_name, 0777), "%s", strerror(errno)); | 
|  | auto cleanup_dir = | 
|  | fit::defer([&dir_name]() { EXPECT_EQ(0, rmdir(dir_name), "%s", strerror(errno)); }); | 
|  | ASSERT_TRUE(fbl::unique_fd(creat(foo_rel, 0666)), "%s", strerror(errno)); | 
|  | auto cleanup_foo = | 
|  | fit::defer([&foo_rel]() { EXPECT_EQ(0, unlink(foo_rel), "%s", strerror(errno)); }); | 
|  |  | 
|  | fbl::unique_fd dir_fd(open(dir_name, O_RDONLY | O_DIRECTORY, 0644)); | 
|  | ASSERT_TRUE(dir_fd, "%s", strerror(errno)); | 
|  |  | 
|  | // Create link using relative paths. | 
|  | constexpr char bar_name[] = "bar", bar_rel[] = "dir/bar"; | 
|  | ASSERT_EQ(0, linkat(dir_fd.get(), foo_name, AT_FDCWD, bar_rel, 0), "%s", strerror(errno)); | 
|  | ASSERT_EQ(0, unlink(bar_rel), "%s", strerror(errno)); | 
|  | ASSERT_EQ(0, linkat(AT_FDCWD, foo_rel, dir_fd.get(), bar_name, 0), "%s", strerror(errno)); | 
|  | ASSERT_EQ(0, unlink(bar_rel), "%s", strerror(errno)); | 
|  |  | 
|  | // Create link using an absolute oldpath (newpath), verifying that olddirfd (newdirfd) is ignored. | 
|  | // We also test that an invalid file descriptor is accepted if the corresponding path is absolute. | 
|  | const std::string foo_abs = fxl::Concatenate({root_abs, "/", foo_rel}); | 
|  | const std::string bar_abs = fxl::Concatenate({root_abs, "/", bar_rel}); | 
|  | for (int olddirfd : {dir_fd.get(), AT_FDCWD, kInvalidFD}) { | 
|  | ASSERT_EQ(0, linkat(olddirfd, foo_abs.c_str(), AT_FDCWD, bar_rel, 0), "%s", strerror(errno)); | 
|  | ASSERT_EQ(0, unlink(bar_rel), "%s", strerror(errno)); | 
|  | } | 
|  | for (int newdirfd : {dir_fd.get(), AT_FDCWD, kInvalidFD}) { | 
|  | ASSERT_EQ(0, linkat(AT_FDCWD, foo_rel, newdirfd, bar_abs.c_str(), 0), "%s", strerror(errno)); | 
|  | ASSERT_EQ(0, unlink(bar_rel), "%s", strerror(errno)); | 
|  | } | 
|  |  | 
|  | // Test errors: an invalid file descriptor is not accepted if the corresponding path is relative. | 
|  | constexpr char baz_name[] = "baz"; | 
|  | ASSERT_EQ(-1, linkat(kInvalidFD, foo_rel, AT_FDCWD, baz_name, 0)); | 
|  | EXPECT_EQ(EBADF, errno, "%s", strerror(errno)); | 
|  | ASSERT_EQ(-1, linkat(AT_FDCWD, foo_rel, kInvalidFD, baz_name, 0)); | 
|  | EXPECT_EQ(EBADF, errno, "%s", strerror(errno)); | 
|  | } | 
|  |  | 
|  | TEST(UnistdTest, LinkAtFollow) { | 
|  | char root_abs[] = "/tmp/fdio-linkat-follow.XXXXXX"; | 
|  | ASSERT_NOT_NULL(mkdtemp(root_abs), "%s", strerror(errno)); | 
|  | auto cleanup_root = | 
|  | fit::defer([&root_abs]() { EXPECT_EQ(0, rmdir(root_abs), "%s", strerror(errno)); }); | 
|  |  | 
|  | const std::string file_abs = fxl::Concatenate({root_abs, "/", "file"}); | 
|  | ASSERT_TRUE(fbl::unique_fd(creat(file_abs.c_str(), 0666)), "%s", strerror(errno)); | 
|  | auto cleanup_file = fit::defer([&file_abs]() { | 
|  | // This must be an ASSERT_* so that errors can be caught by ASSERT_NO_FATAL_FAILURE below. | 
|  | ASSERT_EQ(0, unlink(file_abs.c_str()), "%s", strerror(errno)); | 
|  | }); | 
|  |  | 
|  | // Verify that we can create a hard link to a regular file even if AT_SYMLINK_FOLLOW is set. | 
|  | const std::string hard_abs = fxl::Concatenate({root_abs, "/", "hard"}); | 
|  | ASSERT_EQ(0, linkat(AT_FDCWD, file_abs.c_str(), AT_FDCWD, hard_abs.c_str(), AT_SYMLINK_FOLLOW), | 
|  | "%s", strerror(errno)); | 
|  | ASSERT_EQ(0, unlink(hard_abs.c_str()), "%s", strerror(errno)); | 
|  |  | 
|  | // Create a symlink and test AT_SYMLINK_FOLLOW on it. | 
|  | const std::string sym_abs = fxl::Concatenate({root_abs, "/", "sym"}); | 
|  | #ifndef __Fuchsia__ | 
|  | ASSERT_EQ(0, symlink(file_abs.c_str(), sym_abs.c_str()), "%s", strerror(errno)); | 
|  | auto cleanup_sym = | 
|  | fit::defer([&sym_abs]() { EXPECT_EQ(0, unlink(sym_abs.c_str()), "%s", strerror(errno)); }); | 
|  |  | 
|  | auto expect_file_type_and_unlink = [](const char* path, int expected_file_type) { | 
|  | auto unlink_path = fit::defer([path]() { ASSERT_EQ(0, unlink(path), "%s", strerror(errno)); }); | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, lstat(path, &st), "%s", strerror(errno)); | 
|  | EXPECT_EQ(expected_file_type, st.st_mode & S_IFMT); | 
|  | }; | 
|  |  | 
|  | ASSERT_EQ(0, linkat(AT_FDCWD, sym_abs.c_str(), AT_FDCWD, hard_abs.c_str(), 0), "%s", | 
|  | strerror(errno)); | 
|  | ASSERT_NO_FATAL_FAILURE(expect_file_type_and_unlink(hard_abs.c_str(), S_IFLNK)); | 
|  |  | 
|  | ASSERT_EQ(0, linkat(AT_FDCWD, sym_abs.c_str(), AT_FDCWD, hard_abs.c_str(), AT_SYMLINK_FOLLOW), | 
|  | "%s", strerror(errno)); | 
|  | ASSERT_NO_FATAL_FAILURE(expect_file_type_and_unlink(hard_abs.c_str(), S_IFREG)); | 
|  |  | 
|  | // Make our symlink dangling by removing its target. | 
|  | ASSERT_NO_FATAL_FAILURE(cleanup_file.call()); | 
|  |  | 
|  | ASSERT_EQ(0, linkat(AT_FDCWD, sym_abs.c_str(), AT_FDCWD, hard_abs.c_str(), 0), "%s", | 
|  | strerror(errno)); | 
|  | ASSERT_NO_FATAL_FAILURE(expect_file_type_and_unlink(hard_abs.c_str(), S_IFLNK)); | 
|  |  | 
|  | ASSERT_EQ(-1, linkat(AT_FDCWD, sym_abs.c_str(), AT_FDCWD, hard_abs.c_str(), AT_SYMLINK_FOLLOW)); | 
|  | EXPECT_EQ(ENOENT, errno, "%s", strerror(errno)); | 
|  | #else | 
|  | // Assert that Fuchsia does not support symlinks yet. | 
|  | ASSERT_EQ(-1, symlink(file_abs.c_str(), sym_abs.c_str())); | 
|  | ASSERT_EQ(ENOSYS, errno, "%s", strerror(errno)); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | TEST(UnistdTest, ReadAndWriteWithNegativeOffsets) { | 
|  | const char* filename = "/tmp/read-write-with-negative-offsets-test"; | 
|  | fbl::unique_fd fd(open(filename, O_CREAT | O_RDWR, 0666)); | 
|  | ASSERT_TRUE(fd); | 
|  | ASSERT_EQ(-1, pwrite(fd.get(), "hello", 5, -1)); | 
|  | ASSERT_EQ(EINVAL, errno, "%s", strerror(errno)); | 
|  | char buf[5]; | 
|  | ASSERT_EQ(-1, pwrite(fd.get(), buf, 5, -1)); | 
|  | ASSERT_EQ(EINVAL, errno, "%s", strerror(errno)); | 
|  | } | 
|  |  | 
|  | }  // namespace |