mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 08:25:07 +00:00

This converts the return value of File::read_link() from String to ErrorOr<String>. The rest of the change is to support the potential of an Error being returned and subsequent release of the value when no Error is returned. Unfortunately at this stage none of the places affected can utililize our TRY() macro.
398 lines
12 KiB
C++
398 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Assertions.h>
|
|
#include <AK/Types.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibTest/TestCase.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/uio.h>
|
|
#include <unistd.h>
|
|
|
|
#define EXPECT_ERROR_2(err, syscall, arg1, arg2) \
|
|
do { \
|
|
rc = syscall(arg1, arg2); \
|
|
EXPECT(rc < 0); \
|
|
EXPECT_EQ(errno, err); \
|
|
if (rc >= 0 || errno != err) { \
|
|
warnln(__FILE__ ":{}: Expected " #err ": " #syscall "({}, {}), got rc={}, errno={}", __LINE__, arg1, arg2, rc, errno); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define EXPECT_ERROR_3(err, syscall, arg1, arg2, arg3) \
|
|
do { \
|
|
rc = syscall(arg1, arg2, arg3); \
|
|
EXPECT(rc < 0); \
|
|
EXPECT_EQ(errno, err); \
|
|
if (rc >= 0 || errno != err) { \
|
|
warnln(__FILE__ ":{}: Expected " #err ": " #syscall "({}, {}, {}), got rc={}, errno={}", __LINE__, arg1, arg2, arg3, rc, errno); \
|
|
} \
|
|
} while (0)
|
|
|
|
TEST_CASE(read_from_directory)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
int fd = open("/", O_DIRECTORY | O_RDONLY);
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
EXPECT_ERROR_3(EISDIR, read, fd, buffer, sizeof(buffer));
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(write_to_directory)
|
|
{
|
|
char str[] = "oh frick";
|
|
int fd = open("/", O_DIRECTORY | O_RDONLY);
|
|
if (fd < 0)
|
|
perror("open");
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str));
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(read_from_writeonly)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
int fd = open("/tmp/xxxx123", O_CREAT | O_WRONLY);
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
EXPECT_ERROR_3(EBADF, read, fd, buffer, sizeof(buffer));
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
rc = unlink("/tmp/xxxx123");
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(write_to_readonly)
|
|
{
|
|
char str[] = "hello";
|
|
int fd = open("/tmp/abcd123", O_CREAT | O_RDONLY);
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str));
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
rc = unlink("/tmp/abcd123");
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(read_past_eof)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
int fd = open("/home/anon/README.md", O_RDONLY);
|
|
if (fd < 0)
|
|
perror("open");
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
rc = lseek(fd, 99999, SEEK_SET);
|
|
if (rc < 0)
|
|
perror("lseek");
|
|
rc = read(fd, buffer, sizeof(buffer));
|
|
if (rc < 0)
|
|
perror("read");
|
|
if (rc > 0)
|
|
warnln("read {} bytes past EOF", rc);
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(ftruncate_readonly)
|
|
{
|
|
int fd = open("/tmp/trunctest", O_RDONLY | O_CREAT, 0666);
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
EXPECT_ERROR_2(EBADF, ftruncate, fd, 0);
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
rc = unlink("/tmp/trunctest");
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(ftruncate_negative)
|
|
{
|
|
int fd = open("/tmp/trunctest", O_RDWR | O_CREAT, 0666);
|
|
VERIFY(fd >= 0);
|
|
int rc;
|
|
EXPECT_ERROR_2(EINVAL, ftruncate, fd, -1);
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
rc = unlink("/tmp/trunctest");
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(mmap_directory)
|
|
{
|
|
int fd = open("/tmp", O_RDONLY | O_DIRECTORY);
|
|
VERIFY(fd >= 0);
|
|
auto* ptr = mmap(nullptr, 4096, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
|
|
EXPECT_EQ(ptr, MAP_FAILED);
|
|
if (ptr != MAP_FAILED) {
|
|
warnln("Boo! mmap() of a directory succeeded!");
|
|
}
|
|
EXPECT_EQ(errno, ENODEV);
|
|
if (errno != ENODEV) {
|
|
warnln("Boo! mmap() of a directory gave errno={} instead of ENODEV!", errno);
|
|
return;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
TEST_CASE(tmpfs_read_past_end)
|
|
{
|
|
int fd = open("/tmp/x", O_RDWR | O_CREAT | O_TRUNC, 0600);
|
|
VERIFY(fd >= 0);
|
|
|
|
int rc = ftruncate(fd, 1);
|
|
VERIFY(rc == 0);
|
|
|
|
rc = lseek(fd, 4096, SEEK_SET);
|
|
VERIFY(rc == 4096);
|
|
|
|
char buffer[16];
|
|
int nread = read(fd, buffer, sizeof(buffer));
|
|
if (nread != 0) {
|
|
warnln("Expected 0-length read past end of file in /tmp");
|
|
}
|
|
rc = close(fd);
|
|
VERIFY(rc == 0);
|
|
rc = unlink("/tmp/x");
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(procfs_read_past_end)
|
|
{
|
|
int fd = open("/proc/uptime", O_RDONLY);
|
|
VERIFY(fd >= 0);
|
|
|
|
int rc = lseek(fd, 4096, SEEK_SET);
|
|
VERIFY(rc == 4096);
|
|
|
|
char buffer[16];
|
|
int nread = read(fd, buffer, sizeof(buffer));
|
|
if (nread != 0) {
|
|
warnln("Expected 0-length read past end of file in /proc");
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
TEST_CASE(open_create_device)
|
|
{
|
|
int fd = open("/tmp/fakedevice", (O_RDWR | O_CREAT), (S_IFCHR | 0600));
|
|
VERIFY(fd >= 0);
|
|
|
|
struct stat st;
|
|
int rc = fstat(fd, &st);
|
|
EXPECT(rc >= 0);
|
|
if (rc < 0) {
|
|
perror("stat");
|
|
}
|
|
|
|
EXPECT_EQ(st.st_mode, 0100600);
|
|
if (st.st_mode != 0100600) {
|
|
warnln("Expected mode 0100600 after attempt to create a device node with open(O_CREAT), mode={:o}", st.st_mode);
|
|
}
|
|
rc = unlink("/tmp/fakedevice");
|
|
EXPECT_EQ(rc, 0);
|
|
close(fd);
|
|
EXPECT_EQ(rc, 0);
|
|
}
|
|
|
|
TEST_CASE(unlink_symlink)
|
|
{
|
|
int rc = symlink("/proc/2/foo", "/tmp/linky");
|
|
EXPECT(rc >= 0);
|
|
if (rc < 0) {
|
|
perror("symlink");
|
|
}
|
|
|
|
auto target_or_error = Core::File::read_link("/tmp/linky");
|
|
EXPECT(!target_or_error.is_error());
|
|
|
|
auto target = target_or_error.release_value();
|
|
EXPECT_EQ(target, "/proc/2/foo");
|
|
|
|
rc = unlink("/tmp/linky");
|
|
EXPECT(rc >= 0);
|
|
if (rc < 0) {
|
|
perror("unlink");
|
|
warnln("Expected unlink() of a symlink into an unreadable directory to succeed!");
|
|
}
|
|
}
|
|
|
|
TEST_CASE(tmpfs_eoverflow)
|
|
{
|
|
int fd = open("/tmp/x", O_RDWR | O_CREAT);
|
|
EXPECT(fd >= 0);
|
|
|
|
off_t rc = lseek(fd, INT64_MAX, SEEK_SET);
|
|
EXPECT_EQ(rc, INT64_MAX);
|
|
|
|
char buffer[16] {};
|
|
char empty_buffer[16] {};
|
|
|
|
rc = read(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, -1);
|
|
EXPECT_EQ(errno, EOVERFLOW);
|
|
|
|
[[maybe_unused]] auto ignored = strlcpy(buffer, "abcdefghijklmno", sizeof(buffer) - 1);
|
|
|
|
rc = write(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, -1);
|
|
EXPECT_EQ(errno, EOVERFLOW);
|
|
if (rc >= 0 || errno != EOVERFLOW) {
|
|
warnln("Expected EOVERFLOW when trying to write past INT64_MAX");
|
|
}
|
|
|
|
// ok now, write something to it, and try again
|
|
rc = lseek(fd, 0, SEEK_SET);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = write(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, 16);
|
|
|
|
rc = lseek(fd, INT64_MAX, SEEK_SET);
|
|
EXPECT_EQ(rc, INT64_MAX);
|
|
|
|
memset(buffer, 0, sizeof(buffer));
|
|
rc = read(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, -1);
|
|
EXPECT_EQ(errno, EOVERFLOW);
|
|
if (rc >= 0 || errno != EOVERFLOW) {
|
|
warnln("Expected EOVERFLOW when trying to read past INT64_MAX");
|
|
}
|
|
EXPECT_EQ(0, memcmp(buffer, empty_buffer, sizeof(buffer)));
|
|
|
|
rc = close(fd);
|
|
EXPECT_EQ(rc, 0);
|
|
rc = unlink("/tmp/x");
|
|
EXPECT_EQ(rc, 0);
|
|
}
|
|
|
|
TEST_CASE(tmpfs_massive_file)
|
|
{
|
|
int fd = open("/tmp/x", O_RDWR | O_CREAT);
|
|
EXPECT(fd >= 0);
|
|
|
|
off_t rc = lseek(fd, INT32_MAX, SEEK_SET);
|
|
EXPECT_EQ(rc, INT32_MAX);
|
|
|
|
char buffer[16] {};
|
|
rc = read(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
[[maybe_unused]] auto ignored = strlcpy(buffer, "abcdefghijklmno", sizeof(buffer) - 1);
|
|
|
|
rc = write(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, -1);
|
|
EXPECT_EQ(errno, ENOMEM);
|
|
|
|
// ok now, write something to it, and try again
|
|
rc = lseek(fd, 0, SEEK_SET);
|
|
EXPECT_EQ(rc, 0);
|
|
|
|
rc = write(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, 16);
|
|
|
|
rc = lseek(fd, INT32_MAX, SEEK_SET);
|
|
EXPECT_EQ(rc, INT32_MAX);
|
|
|
|
// FIXME: Should this return EOVERFLOW? Or is a 0 read fine?
|
|
memset(buffer, 0, sizeof(buffer));
|
|
rc = read(fd, buffer, sizeof(buffer));
|
|
EXPECT_EQ(rc, 0);
|
|
EXPECT(buffer != "abcdefghijklmno"sv);
|
|
|
|
rc = close(fd);
|
|
EXPECT_EQ(rc, 0);
|
|
rc = unlink("/tmp/x");
|
|
EXPECT_EQ(rc, 0);
|
|
}
|
|
|
|
TEST_CASE(rmdir_while_inside_dir)
|
|
{
|
|
int rc = mkdir("/home/anon/testdir", 0700);
|
|
VERIFY(rc == 0);
|
|
|
|
rc = chdir("/home/anon/testdir");
|
|
VERIFY(rc == 0);
|
|
|
|
rc = rmdir("/home/anon/testdir");
|
|
VERIFY(rc == 0);
|
|
|
|
int fd = open("x", O_CREAT | O_RDWR, 0600);
|
|
EXPECT(fd < 0);
|
|
EXPECT_EQ(errno, ENOENT);
|
|
if (fd >= 0 || errno != ENOENT) {
|
|
warnln("Expected ENOENT when trying to create a file inside a deleted directory. Got {} with errno={}", fd, errno);
|
|
}
|
|
|
|
rc = chdir("/home/anon");
|
|
VERIFY(rc == 0);
|
|
}
|
|
|
|
TEST_CASE(writev)
|
|
{
|
|
int pipefds[2];
|
|
int rc = pipe(pipefds);
|
|
EXPECT(rc == 0);
|
|
|
|
iovec iov[2];
|
|
iov[0].iov_base = const_cast<void*>((const void*)"Hello");
|
|
iov[0].iov_len = 5;
|
|
iov[1].iov_base = const_cast<void*>((const void*)"Friends");
|
|
iov[1].iov_len = 7;
|
|
int nwritten = writev(pipefds[1], iov, 2);
|
|
EXPECT_EQ(nwritten, 12);
|
|
if (nwritten < 0) {
|
|
perror("writev");
|
|
}
|
|
if (nwritten != 12) {
|
|
warnln("Didn't write 12 bytes to pipe with writev");
|
|
}
|
|
|
|
char buffer[32] {};
|
|
int nread = read(pipefds[0], buffer, sizeof(buffer));
|
|
EXPECT_EQ(nread, 12);
|
|
EXPECT_EQ(buffer, "HelloFriends"sv);
|
|
if (nread != 12 || memcmp(buffer, "HelloFriends", 12)) {
|
|
warnln("Didn't read the expected data from pipe after writev");
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
close(pipefds[0]);
|
|
close(pipefds[1]);
|
|
}
|
|
|
|
TEST_CASE(rmdir_root)
|
|
{
|
|
int rc = rmdir("/");
|
|
EXPECT_EQ(rc, -1);
|
|
EXPECT_EQ(errno, EBUSY);
|
|
if (rc != -1 || errno != EBUSY) {
|
|
warnln("rmdir(/) didn't fail with EBUSY");
|
|
}
|
|
}
|
|
|
|
TEST_CASE(open_silly_things)
|
|
{
|
|
int rc = -1;
|
|
EXPECT_ERROR_2(ENOTDIR, open, "/dev/zero", (O_DIRECTORY | O_RDONLY));
|
|
EXPECT_ERROR_2(EINVAL, open, "/dev/zero", (O_DIRECTORY | O_CREAT | O_RDWR));
|
|
EXPECT_ERROR_2(EEXIST, open, "/dev/zero", (O_CREAT | O_EXCL | O_RDWR));
|
|
EXPECT_ERROR_2(EINVAL, open, "/tmp/abcdef", (O_DIRECTORY | O_CREAT | O_RDWR));
|
|
EXPECT_ERROR_2(EACCES, open, "/proc/all", (O_RDWR));
|
|
EXPECT_ERROR_2(ENOENT, open, "/boof/baaf/nonexistent", (O_CREAT | O_RDWR));
|
|
EXPECT_ERROR_2(EISDIR, open, "/tmp", (O_DIRECTORY | O_RDWR));
|
|
EXPECT_ERROR_2(EPERM, link, "/", "/home/anon/lolroot");
|
|
}
|