/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fud_file.hpp" #include "fud_string.hpp" #include "fud_vector.hpp" #include "test_common.hpp" #include "gtest/gtest.h" #include #include #include #include #include namespace fud { auto rmFile(const auto& filename) -> int { auto result = unlink(filename.c_str()); if (result == -1) { if (errno == ENOENT) { return 0; } } return result; } TEST(FudFile, Basic) { constexpr const char* testDirCName = "/tmp/fud_directory_test"; const auto testDirName{String::makeFromCString(testDirCName).takeOkay()}; ASSERT_TRUE(testDirName.utf8Valid()); constexpr mode_t pathMode = 0777; ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success); auto mkdirResult = mkdir(testDirName.c_str(), pathMode); EXPECT_EQ(mkdirResult, 0); if (mkdirResult != 0) { ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success); return; } String testName1{String::makeFromCStrings(testDirCName, "/", "test1").takeOkay()}; String testName2{String::makeFromCStrings(testDirCName, "/", "test2").takeOkay()}; ASSERT_EQ(rmFile(testName1), 0); ASSERT_EQ(rmFile(testName2), 0); auto fileResult{RegularFile::open(testName1.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt)}; EXPECT_EQ(fileResult.takeErrorOr(FudStatus::Success), FudStatus::NotFound); /* The irony of the ability for TOCTOU bugs to be present in the test does * not escape me. */ auto createFile = [](const auto& filename) -> int { constexpr int allPerms = 0777; auto handleResult = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, allPerms); if (handleResult >= 0) { close(handleResult); } return handleResult; }; ASSERT_GE(createFile(testName1), 0); fileResult = std::move(RegularFile::open(testName1.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt)); ASSERT_TRUE(fileResult.isOkay()); auto file{fileResult.takeOkay()}; ASSERT_EQ(file.close(), FudStatus::Success); ASSERT_EQ(rmFile(testName1), 0); ASSERT_GE(createFile(testName2), 0); ASSERT_EQ(symlink(testName2.c_str(), testName1.c_str()), 0); fileResult = RegularFile::open(testName2.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt); ASSERT_TRUE(fileResult.isOkay()); file = fileResult.takeOkay(); ASSERT_EQ(file.close(), FudStatus::Success); fileResult = RegularFile::open(testName1.asView(), FileAccessMode::Read, OpenFlags{}, NullOpt); ASSERT_TRUE(fileResult.isError()); EXPECT_EQ(fileResult.getError(), FudStatus::Failure); } TEST(FudBufferedFile, OpenReadWrite) { constexpr const char* testDirCName = "/tmp/fud_directory_test"; const auto testDirName{String::makeFromCString(testDirCName).takeOkay()}; ASSERT_TRUE(testDirName.utf8Valid()); constexpr mode_t pathMode = 0777; ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success); auto mkdirResult = mkdir(testDirName.c_str(), pathMode); EXPECT_EQ(mkdirResult, 0); if (mkdirResult != 0) { ASSERT_EQ(removeRecursive(testDirName), FudStatus::Success); return; } String testName{String::makeFromCStrings(testDirCName, "/", "test1").takeOkay()}; auto fileResult{RegularFile::create( testName.asView(), FileAccessMode::ReadWrite, OpenFlags{OpenFlagEnum::Truncate}, Permissions{PermReadWrite, PermReadWrite, PermReadWrite}, true, NullOpt)}; ASSERT_TRUE(fileResult.isOkay()); ASSERT_GT(testName.length(), 8); auto vectorResult{Vector::withSize(8)}; ASSERT_TRUE(vectorResult.isOkay()); auto bufferedFile{BufferedRegularFile::make(fileResult.takeOkay(), vectorResult.takeOkay())}; ASSERT_EQ(bufferedFile.file().size().getOkayOr(std::numeric_limits::max()), 0); auto writeResult = bufferedFile.write( reinterpret_cast(testName.data()), testName.size(), NullOpt); DrainResult expected{testName.size(), FudStatus::Success}; ASSERT_EQ(writeResult, expected); DrainResult nullExpected{0, FudStatus::Success}; ASSERT_EQ(bufferedFile.flush(), nullExpected); ASSERT_EQ(bufferedFile.seekStart(), FudStatus::Success); Vector output{Vector::withSize(testName.size()).takeOkay()}; auto readResult = bufferedFile.read( reinterpret_cast(output.data()), testName.size(), testName.size()); ASSERT_EQ(readResult, expected); EXPECT_EQ(output.size(), testName.size()); EXPECT_EQ(0, compareMem(output.data(), output.size(), testName.data(), testName.size()).takeOkayOr(-1)); EXPECT_EQ(bufferedFile.close(true), FudStatus::Success); ASSERT_EQ(rmFile(testName), 0); } } // namespace fud