1
0
Fork 0
apt/test/libapt/fileutl_test.cc
Daniel Baumann 6810ba718b
Adding upstream version 3.0.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-20 21:10:43 +02:00

399 lines
12 KiB
C++

#include <config.h>
#include <apt-pkg/aptconfiguration.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/strutl.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include "common.h"
#include "file-helpers.h"
static void TestFileFd(mode_t const a_umask, mode_t const ExpectedFilePermission,
unsigned int const filemode, APT::Configuration::Compressor const &compressor)
{
std::string trace;
strprintf(trace, "TestFileFd: Compressor: %s umask: %#o permission: %#o mode: %d", compressor.Name.c_str(), a_umask, ExpectedFilePermission, filemode);
SCOPED_TRACE(trace);
auto const file = createTemporaryFile("filefd-test");
EXPECT_TRUE(RemoveFile("TestFileFd", file.Name()));
FileFd f;
umask(a_umask);
EXPECT_TRUE(f.Open(file.Name(), filemode, compressor));
EXPECT_TRUE(f.IsOpen());
EXPECT_FALSE(f.Failed());
EXPECT_EQ(umask(a_umask), a_umask);
std::string test = "This is a test!\n";
EXPECT_TRUE(f.Write(test.c_str(), test.size()));
EXPECT_TRUE(f.IsOpen());
EXPECT_FALSE(f.Failed());
f.Close();
EXPECT_FALSE(f.IsOpen());
EXPECT_FALSE(f.Failed());
EXPECT_TRUE(f.Open(file.Name(), FileFd::ReadOnly, compressor));
EXPECT_TRUE(f.IsOpen());
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_NE(0u, f.FileSize());
EXPECT_FALSE(f.Failed());
EXPECT_NE(0, f.ModificationTime());
EXPECT_FALSE(f.Failed());
// ensure the memory is as predictably messed up
#define APT_INIT_READBACK \
char readback[20]; \
memset(readback, 'D', sizeof(readback)*sizeof(readback[0])); \
readback[19] = '\0';
#define EXPECT_N_STR(expect, actual) \
EXPECT_EQ(0, strncmp(expect, actual, strlen(expect)));
{
APT_INIT_READBACK
char const * const expect = "DDDDDDDDDDDDDDDDDDD";
EXPECT_STREQ(expect,readback);
EXPECT_N_STR(expect, readback);
}
{
APT_INIT_READBACK
char const * const expect = "This";
EXPECT_TRUE(f.Read(readback, strlen(expect)));
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_N_STR(expect, readback);
EXPECT_EQ(strlen(expect), f.Tell());
}
{
APT_INIT_READBACK
char const * const expect = "test!\n";
EXPECT_TRUE(f.Skip((test.size() - f.Tell()) - strlen(expect)));
EXPECT_TRUE(f.Read(readback, strlen(expect)));
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_N_STR(expect, readback);
EXPECT_EQ(test.size(), f.Tell());
}
// Non-zero backwards seek
{
APT_INIT_READBACK
char const * const expect = "is";
EXPECT_EQ(test.size(), f.Tell());
EXPECT_TRUE(f.Seek(5));
EXPECT_TRUE(f.Read(readback, strlen(expect)));
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_N_STR(expect, readback);
EXPECT_EQ(7u, f.Tell());
}
{
APT_INIT_READBACK
EXPECT_TRUE(f.Seek(0));
EXPECT_FALSE(f.Eof());
EXPECT_TRUE(f.Read(readback, 20, true));
EXPECT_FALSE(f.Failed());
EXPECT_TRUE(f.Eof());
EXPECT_N_STR(test.c_str(), readback);
EXPECT_EQ(f.Size(), f.Tell());
}
{
APT_INIT_READBACK
EXPECT_TRUE(f.Seek(0));
EXPECT_FALSE(f.Eof());
EXPECT_TRUE(f.Read(readback, test.size(), true));
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_N_STR(test.c_str(), readback);
EXPECT_EQ(f.Size(), f.Tell());
}
{
APT_INIT_READBACK
EXPECT_TRUE(f.Seek(0));
EXPECT_FALSE(f.Eof());
unsigned long long actual;
EXPECT_TRUE(f.Read(readback, 20, &actual));
EXPECT_FALSE(f.Failed());
EXPECT_TRUE(f.Eof());
EXPECT_EQ(test.size(), actual);
EXPECT_N_STR(test.c_str(), readback);
EXPECT_EQ(f.Size(), f.Tell());
}
{
APT_INIT_READBACK
EXPECT_TRUE(f.Seek(0));
EXPECT_FALSE(f.Eof());
f.ReadLine(readback, 20);
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_EQ(test, readback);
EXPECT_EQ(f.Size(), f.Tell());
}
{
APT_INIT_READBACK
EXPECT_TRUE(f.Seek(0));
EXPECT_FALSE(f.Eof());
char const * const expect = "This";
f.ReadLine(readback, strlen(expect) + 1);
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_N_STR(expect, readback);
EXPECT_EQ(strlen(expect), f.Tell());
}
#undef APT_INIT_READBACK
{
test.erase(test.length() - 1);
EXPECT_TRUE(f.Seek(0));
EXPECT_FALSE(f.Eof());
std::string line;
EXPECT_TRUE(f.ReadLine(line));
EXPECT_FALSE(f.Failed());
EXPECT_FALSE(f.Eof());
EXPECT_EQ(line.length(), test.length());
EXPECT_EQ(line.length() + 1, f.Tell());
EXPECT_EQ(f.Size(), f.Tell());
EXPECT_EQ(line, test);
}
f.Close();
EXPECT_FALSE(f.IsOpen());
EXPECT_FALSE(f.Failed());
// regression test for permission bug LP: #1304657
struct stat buf;
EXPECT_EQ(0, stat(file.Name().c_str(), &buf));
EXPECT_EQ(ExpectedFilePermission, buf.st_mode & 0777);
}
static void TestFileFd(unsigned int const filemode)
{
auto const compressors = APT::Configuration::getCompressors();
EXPECT_EQ(8u, compressors.size());
bool atLeastOneWasTested = false;
for (auto const &c: compressors)
{
if ((filemode & FileFd::ReadWrite) == FileFd::ReadWrite &&
(c.Name.empty() != true && c.Binary.empty() != true))
continue;
atLeastOneWasTested = true;
TestFileFd(0002, 0664, filemode, c);
TestFileFd(0022, 0644, filemode, c);
TestFileFd(0077, 0600, filemode, c);
TestFileFd(0026, 0640, filemode, c);
}
EXPECT_TRUE(atLeastOneWasTested);
}
TEST(FileUtlTest, FileFD)
{
// testing the (un)compress via pipe, as the 'real' compressors are usually built in via libraries
_config->Set("APT::Compressor::rev::Name", "rev");
_config->Set("APT::Compressor::rev::Extension", ".reversed");
_config->Set("APT::Compressor::rev::Binary", "rev");
_config->Set("APT::Compressor::rev::Cost", 10);
auto const compressors = APT::Configuration::getCompressors(false);
EXPECT_EQ(8u, compressors.size());
EXPECT_TRUE(std::any_of(compressors.begin(), compressors.end(), [](APT::Configuration::Compressor const &c) { return c.Name == "rev"; }));
std::string const startdir = SafeGetCWD();
EXPECT_FALSE(startdir.empty());
std::string tempdir;
createTemporaryDirectory("filefd", tempdir);
EXPECT_EQ(0, chdir(tempdir.c_str()));
TestFileFd(FileFd::WriteOnly | FileFd::Create);
TestFileFd(FileFd::WriteOnly | FileFd::Create | FileFd::Empty);
TestFileFd(FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive);
TestFileFd(FileFd::WriteOnly | FileFd::Atomic);
TestFileFd(FileFd::WriteOnly | FileFd::Create | FileFd::Atomic);
// short-hands for ReadWrite with these modes
TestFileFd(FileFd::WriteEmpty);
TestFileFd(FileFd::WriteAny);
TestFileFd(FileFd::WriteTemp);
TestFileFd(FileFd::WriteAtomic);
EXPECT_EQ(0, chdir(startdir.c_str()));
removeDirectory(tempdir);
}
TEST(FileUtlTest, Glob)
{
std::vector<std::string> files;
// normal match
files = Glob("*MakeLists.txt");
EXPECT_EQ(1u, files.size());
// not there
files = Glob("xxxyyyzzz");
EXPECT_TRUE(files.empty());
EXPECT_FALSE(_error->PendingError());
// many matches (number is a bit random)
files = Glob("*.cc");
EXPECT_LT(10u, files.size());
}
TEST(FileUtlTest, GetTempDir)
{
char const * const envtmp = getenv("TMPDIR");
std::string old_tmpdir;
if (envtmp != NULL)
old_tmpdir = envtmp;
unsetenv("TMPDIR");
EXPECT_EQ("/tmp", GetTempDir());
setenv("TMPDIR", "", 1);
EXPECT_EQ("/tmp", GetTempDir());
setenv("TMPDIR", "/not-there-no-really-not", 1);
EXPECT_EQ("/tmp", GetTempDir());
// root can access everything, so /usr will be accepted
if (geteuid() != 0)
{
// here but not accessible for non-roots
setenv("TMPDIR", "/usr", 1);
EXPECT_EQ("/tmp", GetTempDir());
}
// files are no good for tmpdirs, too
setenv("TMPDIR", "/dev/null", 1);
EXPECT_EQ("/tmp", GetTempDir());
setenv("TMPDIR", "/var/tmp", 1);
EXPECT_EQ("/var/tmp", GetTempDir());
unsetenv("TMPDIR");
if (old_tmpdir.empty() == false)
setenv("TMPDIR", old_tmpdir.c_str(), 1);
}
TEST(FileUtlTest, Popen)
{
FileFd Fd;
pid_t Child;
char buf[1024];
std::string s;
unsigned long long n = 0;
std::vector<std::string> OpenFds;
// count Fds to ensure we don't have a resource leak
if(FileExists("/proc/self/fd"))
OpenFds = Glob("/proc/self/fd/*");
// output something
const char* Args[10] = {"echo", "meepmeep", NULL};
EXPECT_TRUE(Popen(Args, Fd, Child, FileFd::ReadOnly));
EXPECT_TRUE(Fd.Read(buf, sizeof(buf)-1, &n));
buf[n] = 0;
EXPECT_NE(n, 0u);
EXPECT_STREQ(buf, "meepmeep\n");
// wait for the child to exit and cleanup
EXPECT_TRUE(ExecWait(Child, "PopenRead"));
EXPECT_TRUE(Fd.Close());
// ensure that after a close all is good again
if(FileExists("/proc/self/fd"))
{
EXPECT_EQ(Glob("/proc/self/fd/*").size(), OpenFds.size());
}
// ReadWrite is not supported
_error->PushToStack();
EXPECT_FALSE(Popen(Args, Fd, Child, FileFd::ReadWrite));
EXPECT_FALSE(Fd.IsOpen());
EXPECT_FALSE(Fd.Failed());
EXPECT_TRUE(_error->PendingError());
_error->RevertToStack();
// write something
Args[0] = "/bin/bash";
Args[1] = "-c";
Args[2] = "read";
Args[3] = NULL;
EXPECT_TRUE(Popen(Args, Fd, Child, FileFd::WriteOnly));
s = "\n";
EXPECT_TRUE(Fd.Write(s.c_str(), s.length()));
EXPECT_TRUE(Fd.Close());
EXPECT_FALSE(Fd.IsOpen());
EXPECT_FALSE(Fd.Failed());
EXPECT_TRUE(ExecWait(Child, "PopenWrite"));
}
TEST(FileUtlTest, flAbsPath)
{
std::string cwd = SafeGetCWD();
int res = chdir("/etc/");
EXPECT_EQ(res, 0);
std::string p = flAbsPath("passwd");
EXPECT_EQ(p, "/etc/passwd");
res = chdir(cwd.c_str());
EXPECT_EQ(res, 0);
}
static void TestDevNullFileFd(unsigned int const filemode)
{
SCOPED_TRACE(filemode);
FileFd f("/dev/null", filemode);
EXPECT_FALSE(f.Failed());
EXPECT_TRUE(f.IsOpen());
EXPECT_TRUE(f.IsOpen());
std::string test = "This is a test!\n";
EXPECT_TRUE(f.Write(test.c_str(), test.size()));
EXPECT_TRUE(f.IsOpen());
EXPECT_FALSE(f.Failed());
f.Close();
EXPECT_FALSE(f.IsOpen());
EXPECT_FALSE(f.Failed());
}
TEST(FileUtlTest, WorkingWithDevNull)
{
TestDevNullFileFd(FileFd::WriteOnly | FileFd::Create);
TestDevNullFileFd(FileFd::WriteOnly | FileFd::Create | FileFd::Empty);
TestDevNullFileFd(FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive);
TestDevNullFileFd(FileFd::WriteOnly | FileFd::Atomic);
TestDevNullFileFd(FileFd::WriteOnly | FileFd::Create | FileFd::Atomic);
// short-hands for ReadWrite with these modes
TestDevNullFileFd(FileFd::WriteEmpty);
TestDevNullFileFd(FileFd::WriteAny);
TestDevNullFileFd(FileFd::WriteTemp);
TestDevNullFileFd(FileFd::WriteAtomic);
}
constexpr char const * const TESTSTRING = "This is a test";
static void TestFailingAtomicKeepsFile(char const * const label, std::string const &filename)
{
SCOPED_TRACE(label);
EXPECT_TRUE(FileExists(filename));
FileFd fd;
EXPECT_TRUE(fd.Open(filename, FileFd::ReadOnly));
char buffer[50];
EXPECT_NE(nullptr, fd.ReadLine(buffer, sizeof(buffer)));
EXPECT_STREQ(TESTSTRING, buffer);
}
TEST(FileUtlTest, FailingAtomic)
{
auto const file = createTemporaryFile("failingatomic", TESTSTRING);
TestFailingAtomicKeepsFile("init", file.Name());
FileFd f;
EXPECT_TRUE(f.Open(file.Name(), FileFd::ReadWrite | FileFd::Atomic));
f.EraseOnFailure();
EXPECT_FALSE(f.Failed());
EXPECT_TRUE(f.IsOpen());
TestFailingAtomicKeepsFile("before-fail", file.Name());
EXPECT_TRUE(f.Write("Bad file write", 10));
f.OpFail();
EXPECT_TRUE(f.Failed());
TestFailingAtomicKeepsFile("after-fail", file.Name());
EXPECT_TRUE(f.Close());
TestFailingAtomicKeepsFile("closed", file.Name());
}