1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 08:25:07 +00:00
serenity/Tests/LibC/TestIo.cpp
Kenneth Myhra 4a57be824c Userland+Tests: Convert File::read_link() from String to ErrorOr<String>
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.
2022-03-24 11:57:51 +01:00

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");
}