blob: 2d23a47b8f6945faee44accde09758a98b936c6d [file] [log] [blame]
// 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