blob: cc1c437dbfa4906d10bd6f302536f21b4cd2ab37 [file] [log] [blame]
// Copyright (c) 2013 The Chromium 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 <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <string>
#include "gtest/gtest.h"
#include "nacl_io/ioctl.h"
#include "nacl_io/mount.h"
#include "nacl_io/mount_dev.h"
#include "nacl_io/mount_mem.h"
#include "nacl_io/osdirent.h"
#include "nacl_io/osunistd.h"
using namespace nacl_io;
namespace {
class MountMemMock : public MountMem {
public:
MountMemMock() {
StringMap_t map;
EXPECT_EQ(0, Init(1, map, NULL));
}
int num_nodes() { return (int) inode_pool_.size(); }
};
class MountDevMock : public MountDev {
public:
MountDevMock() {
StringMap_t map;
Init(1, map, NULL);
}
int num_nodes() { return (int) inode_pool_.size(); }
};
} // namespace
#define NULL_NODE ((MountNode*) NULL)
TEST(MountTest, Sanity) {
MountMemMock* mnt = new MountMemMock();
ScopedMountNode file;
ScopedMountNode root;
ScopedMountNode result_node;
size_t result_size = 0;
int result_bytes = 0;
char buf1[1024];
// A memory mount starts with one directory node: the root.
EXPECT_EQ(1, mnt->num_nodes());
// Fail to open non existent file
EXPECT_EQ(ENOENT, mnt->Access(Path("/foo"), R_OK | W_OK));
EXPECT_EQ(ENOENT, mnt->Open(Path("/foo"), O_RDWR, &result_node));
EXPECT_EQ(NULL, result_node.get());
EXPECT_EQ(1, mnt->num_nodes());
// Create a file
EXPECT_EQ(0, mnt->Open(Path("/foo"), O_RDWR | O_CREAT, &file));
EXPECT_NE(NULL_NODE, file.get());
if (file == NULL)
return;
// We now have a directory and a file. The file has a two references
// one returned to the test, one for the name->inode map.
EXPECT_EQ(2, mnt->num_nodes());
EXPECT_EQ(2, file->RefCount());
EXPECT_EQ(0, mnt->Access(Path("/foo"), R_OK | W_OK));
EXPECT_EQ(EACCES, mnt->Access(Path("/foo"), X_OK));
// Write access should be allowed on the root directory.
EXPECT_EQ(0, mnt->Access(Path("/"), R_OK | W_OK));
EXPECT_EQ(EACCES, mnt->Access(Path("/"), X_OK));
// Open the root directory for write should fail.
EXPECT_EQ(EISDIR, mnt->Open(Path("/"), O_RDWR, &root));
EXPECT_EQ(2, mnt->num_nodes());
// Open the root directory, should not create a new file
EXPECT_EQ(0, mnt->Open(Path("/"), O_RDONLY, &root));
EXPECT_EQ(2, mnt->num_nodes());
EXPECT_NE(NULL_NODE, root.get());
if (NULL != root) {
struct dirent dirs[2];
int len;
EXPECT_EQ(0, root->GetDents(0, dirs, sizeof(dirs), &len));
EXPECT_EQ(sizeof(struct dirent), len);
}
// Fail to re-create the same file
EXPECT_EQ(EEXIST,
mnt->Open(Path("/foo"), O_RDWR | O_CREAT | O_EXCL, &result_node));
EXPECT_EQ(NULL_NODE, result_node.get());
EXPECT_EQ(2, mnt->num_nodes());
// Fail to create a directory with the same name
EXPECT_EQ(EEXIST, mnt->Mkdir(Path("/foo"), O_RDWR));
EXPECT_EQ(2, mnt->num_nodes());
// Attempt to READ/WRITE
EXPECT_EQ(0, file->GetSize(&result_size));
EXPECT_EQ(0, result_size);
EXPECT_EQ(0, file->Write(0, buf1, sizeof(buf1), &result_bytes));
EXPECT_EQ(sizeof(buf1), result_bytes);
EXPECT_EQ(0, file->GetSize(&result_size));
EXPECT_EQ(sizeof(buf1), result_size);
EXPECT_EQ(0, file->Read(0, buf1, sizeof(buf1), &result_bytes));
EXPECT_EQ(sizeof(buf1), result_bytes);
EXPECT_EQ(2, mnt->num_nodes());
EXPECT_EQ(2, file->RefCount());
// Attempt to open the same file, create another ref to it, but does not
// create a new file.
EXPECT_EQ(0, mnt->Open(Path("/foo"), O_RDWR | O_CREAT, &result_node));
EXPECT_EQ(3, file->RefCount());
EXPECT_EQ(2, mnt->num_nodes());
EXPECT_EQ(file.get(), result_node.get());
EXPECT_EQ(0, file->GetSize(&result_size));
EXPECT_EQ(sizeof(buf1), result_size);
// Remove our references so that only the Mount holds it
file.reset();
result_node.reset();
EXPECT_EQ(2, mnt->num_nodes());
// This should have deleted the object
EXPECT_EQ(0, mnt->Unlink(Path("/foo")));
EXPECT_EQ(1, mnt->num_nodes());
// We should fail to find it
EXPECT_EQ(ENOENT, mnt->Unlink(Path("/foo")));
EXPECT_EQ(1, mnt->num_nodes());
// Recreate foo as a directory
EXPECT_EQ(0, mnt->Mkdir(Path("/foo"), O_RDWR));
EXPECT_EQ(2, mnt->num_nodes());
// Create a file (exclusively)
EXPECT_EQ(0, mnt->Open(Path("/foo/bar"), O_RDWR | O_CREAT | O_EXCL, &file));
EXPECT_NE(NULL_NODE, file.get());
if (NULL == file)
return;
EXPECT_EQ(2, file->RefCount());
EXPECT_EQ(3, mnt->num_nodes());
// Attempt to delete the directory and fail
EXPECT_EQ(ENOTEMPTY, mnt->Rmdir(Path("/foo")));
EXPECT_EQ(2, root->RefCount());
EXPECT_EQ(2, file->RefCount());
EXPECT_EQ(3, mnt->num_nodes());
// Unlink the file, we should have the only file ref at this point.
EXPECT_EQ(0, mnt->Unlink(Path("/foo/bar")));
EXPECT_EQ(2, root->RefCount());
EXPECT_EQ(1, file->RefCount());
EXPECT_EQ(3, mnt->num_nodes());
// Deref the file, to make it go away
file.reset();
EXPECT_EQ(2, mnt->num_nodes());
// Deref the directory
EXPECT_EQ(0, mnt->Rmdir(Path("/foo")));
EXPECT_EQ(1, mnt->num_nodes());
// Verify the directory is gone
EXPECT_EQ(ENOENT, mnt->Access(Path("/foo"), F_OK));
EXPECT_EQ(ENOENT, mnt->Open(Path("/foo"), O_RDWR, &file));
EXPECT_EQ(NULL_NODE, file.get());
}
TEST(MountTest, MemMountRemove) {
MountMemMock* mnt = new MountMemMock();
ScopedMountNode file;
ScopedMountNode result_node;
EXPECT_EQ(0, mnt->Mkdir(Path("/dir"), O_RDWR));
EXPECT_EQ(0, mnt->Open(Path("/file"), O_RDWR | O_CREAT | O_EXCL, &file));
EXPECT_NE(NULL_NODE, file.get());
EXPECT_EQ(3, mnt->num_nodes());
file.reset();
EXPECT_EQ(0, mnt->Remove(Path("/dir")));
EXPECT_EQ(2, mnt->num_nodes());
EXPECT_EQ(0, mnt->Remove(Path("/file")));
EXPECT_EQ(1, mnt->num_nodes());
EXPECT_EQ(ENOENT,
mnt->Open(Path("/dir/foo"), O_CREAT | O_RDWR, &result_node));
EXPECT_EQ(NULL_NODE, result_node.get());
EXPECT_EQ(ENOENT, mnt->Open(Path("/file"), O_RDONLY, &result_node));
EXPECT_EQ(NULL_NODE, result_node.get());
}
TEST(MountTest, DevAccess) {
// Should not be able to open non-existent file.
MountDevMock* mnt = new MountDevMock();
ASSERT_EQ(ENOENT, mnt->Access(Path("/foo"), F_OK));
}
TEST(MountTest, DevNull) {
MountDevMock* mnt = new MountDevMock();
ScopedMountNode dev_null;
int result_bytes = 0;
ASSERT_EQ(0, mnt->Access(Path("/null"), R_OK | W_OK));
ASSERT_EQ(EACCES, mnt->Access(Path("/null"), X_OK));
ASSERT_EQ(0, mnt->Open(Path("/null"), O_RDWR, &dev_null));
ASSERT_NE(NULL_NODE, dev_null.get());
// Writing to /dev/null should write everything.
const char msg[] = "Dummy test message.";
EXPECT_EQ(0, dev_null->Write(0, &msg[0], strlen(msg), &result_bytes));
EXPECT_EQ(strlen(msg), result_bytes);
// Reading from /dev/null should read nothing.
const int kBufferLength = 100;
char buffer[kBufferLength];
EXPECT_EQ(0, dev_null->Read(0, &buffer[0], kBufferLength, &result_bytes));
EXPECT_EQ(0, result_bytes);
}
TEST(MountTest, DevZero) {
MountDevMock* mnt = new MountDevMock();
ScopedMountNode dev_zero;
int result_bytes = 0;
ASSERT_EQ(0, mnt->Access(Path("/zero"), R_OK | W_OK));
ASSERT_EQ(EACCES, mnt->Access(Path("/zero"), X_OK));
ASSERT_EQ(0, mnt->Open(Path("/zero"), O_RDWR, &dev_zero));
ASSERT_NE(NULL_NODE, dev_zero.get());
// Writing to /dev/zero should write everything.
const char msg[] = "Dummy test message.";
EXPECT_EQ(0, dev_zero->Write(0, &msg[0], strlen(msg), &result_bytes));
EXPECT_EQ(strlen(msg), result_bytes);
// Reading from /dev/zero should read all zeroes.
const int kBufferLength = 100;
char buffer[kBufferLength];
// First fill with all 1s.
memset(&buffer[0], 0x1, kBufferLength);
EXPECT_EQ(0, dev_zero->Read(0, &buffer[0], kBufferLength, &result_bytes));
EXPECT_EQ(kBufferLength, result_bytes);
char zero_buffer[kBufferLength];
memset(&zero_buffer[0], 0, kBufferLength);
EXPECT_EQ(0, memcmp(&buffer[0], &zero_buffer[0], kBufferLength));
}
// Disabled due to intermittent failures on linux: http://crbug.com/257257
TEST(MountTest, DISABLED_DevUrandom) {
MountDevMock* mnt = new MountDevMock();
ScopedMountNode dev_urandom;
int result_bytes = 0;
ASSERT_EQ(0, mnt->Access(Path("/urandom"), R_OK | W_OK));
ASSERT_EQ(EACCES, mnt->Access(Path("/urandom"), X_OK));
ASSERT_EQ(0, mnt->Open(Path("/urandom"), O_RDWR, &dev_urandom));
ASSERT_NE(NULL_NODE, dev_urandom.get());
// Writing to /dev/urandom should write everything.
const char msg[] = "Dummy test message.";
EXPECT_EQ(0, dev_urandom->Write(0, &msg[0], strlen(msg), &result_bytes));
EXPECT_EQ(strlen(msg), result_bytes);
// Reading from /dev/urandom should read random bytes.
const int kSampleBatches = 1000;
const int kSampleBatchSize = 1000;
const int kTotalSamples = kSampleBatches * kSampleBatchSize;
int byte_count[256] = {0};
unsigned char buffer[kSampleBatchSize];
for (int batch = 0; batch < kSampleBatches; ++batch) {
int bytes_read = 0;
EXPECT_EQ(0,
dev_urandom->Read(0, &buffer[0], kSampleBatchSize, &bytes_read));
EXPECT_EQ(kSampleBatchSize, bytes_read);
for (int i = 0; i < bytes_read; ++i) {
byte_count[buffer[i]]++;
}
}
double expected_count = kTotalSamples / 256.;
double chi_squared = 0;
for (int i = 0; i < 256; ++i) {
double difference = byte_count[i] - expected_count;
chi_squared += difference * difference / expected_count;
}
// Approximate chi-squared value for p-value 0.05, 255 degrees-of-freedom.
EXPECT_LE(chi_squared, 293.24);
}
TEST(MountTest, DevTty) {
MountDevMock* mnt = new MountDevMock();
ScopedMountNode dev_tty;
ASSERT_EQ(0, mnt->Access(Path("/tty"), R_OK | W_OK));
ASSERT_EQ(EACCES, mnt->Access(Path("/tty"), X_OK));
ASSERT_EQ(0, mnt->Open(Path("/tty"), O_RDWR, &dev_tty));
ASSERT_NE(NULL_NODE, dev_tty.get());
// 123 is not a valid ioctl request.
EXPECT_EQ(EINVAL, dev_tty->Ioctl(123, NULL));
// TIOCNACLPREFIX is, it should set the prefix.
std::string prefix("__my_awesome_prefix__");
EXPECT_EQ(0, dev_tty->Ioctl(TIOCNACLPREFIX,
const_cast<char*>(prefix.c_str())));
// Now let's try sending some data over.
// First we create the message.
std::string content("hello, how are you?");
std::string message = prefix.append(content);
struct tioc_nacl_input_string packaged_message;
packaged_message.length = message.size();
packaged_message.buffer = message.data();
// Now we make buffer we'll read into.
// We fill the buffer and a backup buffer with arbitrary data
// and compare them after reading to make sure read doesn't
// clobber parts of the buffer it shouldn't.
int bytes_read;
char buffer[100];
char backup_buffer[100];
memset(buffer, 'a', 100);
memset(backup_buffer, 'a', 100);
// Now we actually send the data
EXPECT_EQ(0, dev_tty->Ioctl(TIOCNACLINPUT,
reinterpret_cast<char*>(&packaged_message)));
// We read a small chunk first to ensure it doesn't give us
// more than we ask for.
EXPECT_EQ(0, dev_tty->Read(0, buffer, 5, &bytes_read));
EXPECT_EQ(bytes_read, 5);
EXPECT_EQ(0, memcmp(content.data(), buffer, 5));
EXPECT_EQ(0, memcmp(buffer + 5, backup_buffer + 5, 95));
// Now we ask for more data than is left in the tty, to ensure
// it doesn't give us more than is there.
EXPECT_EQ(0, dev_tty->Read(0, buffer + 5, 95, &bytes_read));
EXPECT_EQ(bytes_read, content.size() - 5);
EXPECT_EQ(0, memcmp(content.data(), buffer, content.size()));
EXPECT_EQ(0, memcmp(buffer + content.size(),
backup_buffer + content.size(),
100 - content.size()));
// Now we try to send something with an invalid prefix
std::string bogus_message("Woah there, this message has no valid prefix");
struct tioc_nacl_input_string bogus_pack;
bogus_pack.length = bogus_message.size();
bogus_pack.buffer = bogus_message.data();
EXPECT_EQ(ENOTTY, dev_tty->Ioctl(TIOCNACLINPUT,
reinterpret_cast<char*>(&bogus_pack)));
}