diff options
author | Brian Geffon <bgeffon@google.com> | 2018-12-10 14:41:40 -0800 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-12-10 14:42:34 -0800 |
commit | d3bc79bc8438206ac6a14fde4eaa288fc07eee82 (patch) | |
tree | e820398591bfd1503456e877fa0c2bdd0f994959 /test/util | |
parent | 833edbd10b49db1f934dcb2495dcb41c1310eea4 (diff) |
Open source system call tests.
PiperOrigin-RevId: 224886231
Change-Id: I0fccb4d994601739d8b16b1d4e6b31f40297fb22
Diffstat (limited to 'test/util')
32 files changed, 4983 insertions, 0 deletions
diff --git a/test/util/BUILD b/test/util/BUILD new file mode 100644 index 000000000..e4eec4ab9 --- /dev/null +++ b/test/util/BUILD @@ -0,0 +1,239 @@ +package( + default_visibility = ["//:sandbox"], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "capability_util", + testonly = 1, + srcs = ["capability_util.cc"], + hdrs = ["capability_util.h"], + deps = [ + ":cleanup", + ":memory_util", + ":posix_error", + ":save_util", + ":test_util", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "file_descriptor", + testonly = 1, + hdrs = ["file_descriptor.h"], + deps = [ + ":logging", + ":posix_error", + ":save_util", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "proc_util", + testonly = 1, + srcs = ["proc_util.cc"], + hdrs = ["proc_util.h"], + deps = [ + ":fs_util", + ":posix_error", + ":test_util", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "cleanup", + testonly = 1, + hdrs = ["cleanup.h"], +) + +cc_library( + name = "fs_util", + testonly = 1, + srcs = ["fs_util.cc"], + hdrs = ["fs_util.h"], + deps = [ + ":cleanup", + ":file_descriptor", + ":posix_error", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "fs_util_test", + size = "small", + srcs = ["fs_util_test.cc"], + deps = [ + ":fs_util", + ":posix_error", + ":temp_path", + ":test_util", + ], +) + +cc_library( + name = "logging", + testonly = 1, + srcs = ["logging.cc"], + hdrs = ["logging.h"], +) + +cc_library( + name = "memory_util", + testonly = 1, + hdrs = ["memory_util.h"], + deps = [ + ":logging", + ":posix_error", + ":save_util", + ":test_util", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + +cc_library( + name = "mount_util", + testonly = 1, + hdrs = ["mount_util.h"], + deps = [ + ":cleanup", + ":posix_error", + ":test_util", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "save_util", + testonly = 1, + srcs = ["save_util.cc"], + hdrs = ["save_util.h"], +) + +cc_library( + name = "multiprocess_util", + testonly = 1, + srcs = ["multiprocess_util.cc"], + hdrs = ["multiprocess_util.h"], + deps = [ + ":cleanup", + ":file_descriptor", + ":posix_error", + ":save_util", + ":test_util", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "posix_error", + testonly = 1, + srcs = ["posix_error.cc"], + hdrs = ["posix_error.h"], + deps = [ + ":logging", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:variant", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "posix_error_test", + size = "small", + srcs = ["posix_error_test.cc"], + deps = [":posix_error"], +) + +cc_library( + name = "signal_util", + testonly = 1, + srcs = ["signal_util.cc"], + hdrs = ["signal_util.h"], + deps = [ + ":cleanup", + ":posix_error", + ":test_util", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "temp_path", + testonly = 1, + srcs = ["temp_path.cc"], + hdrs = ["temp_path.h"], + deps = [ + ":fs_util", + ":posix_error", + ":test_util", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "test_util", + testonly = 1, + srcs = ["test_util.cc"], + hdrs = ["test_util.h"], + deps = [ + ":fs_util", + ":logging", + ":posix_error", + ":save_util", + "@com_github_gflags_gflags//:gflags", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + "@com_google_glog//:glog", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "thread_util", + testonly = 1, + hdrs = ["thread_util.h"], + deps = [":logging"], +) + +cc_library( + name = "timer_util", + testonly = 1, + srcs = ["timer_util.cc"], + hdrs = ["timer_util.h"], + deps = [ + ":cleanup", + ":logging", + ":posix_error", + ":test_util", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "test_util_test", + size = "small", + srcs = ["test_util_test.cc"], + deps = [":test_util"], +) + +cc_library( + name = "test_main", + testonly = 1, + srcs = ["test_main.cc"], + deps = [":test_util"], +) diff --git a/test/util/capability_util.cc b/test/util/capability_util.cc new file mode 100644 index 000000000..0656775d6 --- /dev/null +++ b/test/util/capability_util.cc @@ -0,0 +1,79 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/capability_util.h" + +#include <linux/capability.h> +#include <sched.h> +#include <sys/mman.h> +#include <sys/wait.h> + +#include "absl/strings/str_cat.h" +#include "test/util/memory_util.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<bool> CanCreateUserNamespace() { + // The most reliable way to determine if userns creation is possible is by + // trying to create one; see below. + ASSIGN_OR_RETURN_ERRNO( + auto child_stack, + MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)); + int const child_pid = + clone(+[](void*) { return 0; }, + reinterpret_cast<void*>(child_stack.addr() + kPageSize), + CLONE_NEWUSER | SIGCHLD, /* arg = */ nullptr); + if (child_pid > 0) { + int status; + int const ret = waitpid(child_pid, &status, /* options = */ 0); + MaybeSave(); + if (ret < 0) { + return PosixError(errno, "waitpid"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + return PosixError( + ESRCH, absl::StrCat("child process exited with status ", status)); + } + return true; + } else if (errno == EPERM) { + // Per clone(2), EPERM can be returned if: + // + // - "CLONE_NEWUSER was specified in flags, but either the effective user ID + // or the effective group ID of the caller does not have a mapping in the + // parent namespace (see user_namespaces(7))." + // + // - "(since Linux 3.9) CLONE_NEWUSER was specified in flags and the caller + // is in a chroot environment (i.e., the caller's root directory does + // not match the root directory of the mount namespace in which it + // resides)." + LOG(INFO) << "clone(CLONE_NEWUSER) failed with EPERM"; + return false; + } else if (errno == EUSERS) { + // "(since Linux 3.11) CLONE_NEWUSER was specified in flags, and the call + // would cause the limit on the number of nested user namespaces to be + // exceeded. See user_namespaces(7)." + LOG(INFO) << "clone(CLONE_NEWUSER) failed with EUSERS"; + return false; + } else { + // Unexpected error code; indicate an actual error. + return PosixError(errno, "clone(CLONE_NEWUSER)"); + } +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/capability_util.h b/test/util/capability_util.h new file mode 100644 index 000000000..8708f5e69 --- /dev/null +++ b/test/util/capability_util.h @@ -0,0 +1,101 @@ +// Copyright 2018 Google LLC +// +// 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. + +// Utilities for testing capabilties. + +#ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ +#define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ + +#include <errno.h> +#include <linux/capability.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +#ifndef _LINUX_CAPABILITY_VERSION_3 +#error Expecting _LINUX_CAPABILITY_VERSION_3 support +#endif + +namespace gvisor { +namespace testing { + +// HaveCapability returns true if the process has the specified EFFECTIVE +// capability. +inline PosixErrorOr<bool> HaveCapability(int cap) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0; +} + +// SetCapability sets the specified EFFECTIVE capability. +inline PosixError SetCapability(int cap, bool set) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + if (set) { + caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap); + } else { + caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); + } + header = {_LINUX_CAPABILITY_VERSION_3, 0}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); + MaybeSave(); + + return NoError(); +} + +// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE +// capabilities must be a subset of PERMITTED, so those are dropped as well. +inline PosixError DropPermittedCapability(int cap) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); + caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap); + + header = {_LINUX_CAPABILITY_VERSION_3, 0}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); + MaybeSave(); + + return NoError(); +} + +PosixErrorOr<bool> CanCreateUserNamespace(); + +} // namespace testing +} // namespace gvisor +#endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ diff --git a/test/util/cleanup.h b/test/util/cleanup.h new file mode 100644 index 000000000..fb4724f97 --- /dev/null +++ b/test/util/cleanup.h @@ -0,0 +1,61 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_CLEANUP_H_ +#define GVISOR_TEST_UTIL_CLEANUP_H_ + +#include <functional> +#include <utility> + +namespace gvisor { +namespace testing { + +class Cleanup { + public: + Cleanup() : released_(true) {} + explicit Cleanup(std::function<void()>&& callback) : cb_(callback) {} + + Cleanup(Cleanup&& other) { + released_ = other.released_; + cb_ = other.Release(); + } + + Cleanup& operator=(Cleanup&& other) { + released_ = other.released_; + cb_ = other.Release(); + return *this; + } + + ~Cleanup() { + if (!released_) { + cb_(); + } + } + + std::function<void()>&& Release() { + released_ = true; + return std::move(cb_); + } + + private: + Cleanup(Cleanup const& other) = delete; + + bool released_ = false; + std::function<void(void)> cb_; +}; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_CLEANUP_H_ diff --git a/test/util/file_descriptor.h b/test/util/file_descriptor.h new file mode 100644 index 000000000..be8812d01 --- /dev/null +++ b/test/util/file_descriptor.h @@ -0,0 +1,134 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_ +#define GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <string> + +#include "gmock/gmock.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "test/util/logging.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" + +namespace gvisor { +namespace testing { + +// FileDescriptor is an RAII type class which takes ownership of a file +// descriptor. It will close the FD when this object goes out of scope. +class FileDescriptor { + public: + // Constructs an empty FileDescriptor (one that does not own a file + // descriptor). + FileDescriptor() = default; + + // Constructs a FileDescriptor that owns fd. If fd is negative, constructs an + // empty FileDescriptor. + explicit FileDescriptor(int fd) { set_fd(fd); } + + FileDescriptor(FileDescriptor&& orig) : fd_(orig.release()) {} + + FileDescriptor& operator=(FileDescriptor&& orig) { + reset(orig.release()); + return *this; + } + + PosixErrorOr<FileDescriptor> Dup() const { + if (fd_ < 0) { + return PosixError(EINVAL, "Attempting to Dup unset fd"); + } + + int fd = dup(fd_); + if (fd < 0) { + return PosixError(errno, absl::StrCat("dup ", fd_)); + } + MaybeSave(); + return FileDescriptor(fd); + } + + FileDescriptor(FileDescriptor const& other) = delete; + FileDescriptor& operator=(FileDescriptor const& other) = delete; + + ~FileDescriptor() { reset(); } + + // If this object is non-empty, returns the owned file descriptor. (Ownership + // is retained by the FileDescriptor.) Otherwise returns -1. + int get() const { return fd_; } + + // If this object is non-empty, transfers ownership of the file descriptor to + // the caller and returns it. Otherwise returns -1. + int release() { + int const fd = fd_; + fd_ = -1; + return fd; + } + + // If this object is non-empty, closes the owned file descriptor (recording a + // test failure if the close fails). + void reset() { reset(-1); } + + // Like no-arg reset(), but the FileDescriptor takes ownership of fd after + // closing its existing file descriptor. + void reset(int fd) { + if (fd_ >= 0) { + TEST_PCHECK(close(fd_) == 0); + MaybeSave(); + } + set_fd(fd); + } + + private: + // Wrapper that coerces negative fd values other than -1 to -1 so that get() + // etc. return -1. + void set_fd(int fd) { fd_ = std::max(fd, -1); } + + int fd_ = -1; +}; + +// Wrapper around open(2) that returns a FileDescriptor. +inline PosixErrorOr<FileDescriptor> Open(std::string const& path, int flags, + mode_t mode = 0) { + int fd = open(path.c_str(), flags, mode); + if (fd < 0) { + return PosixError(errno, absl::StrFormat("open(%s, %#x, %#o)", path.c_str(), + flags, mode)); + } + MaybeSave(); + return FileDescriptor(fd); +} + +// Wrapper around openat(2) that returns a FileDescriptor. +inline PosixErrorOr<FileDescriptor> OpenAt(int dirfd, std::string const& path, + int flags, mode_t mode = 0) { + int fd = openat(dirfd, path.c_str(), flags, mode); + if (fd < 0) { + return PosixError(errno, absl::StrFormat("openat(%d, %s, %#x, %#o)", dirfd, + path, flags, mode)); + } + MaybeSave(); + return FileDescriptor(fd); +} + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_FILE_DESCRIPTOR_H_ diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc new file mode 100644 index 000000000..e7e8be1d8 --- /dev/null +++ b/test/util/fs_util.cc @@ -0,0 +1,585 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/fs_util.h" + +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "gmock/gmock.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "test/util/cleanup.h" +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +namespace { +PosixError WriteContentsToFD(int fd, absl::string_view contents) { + int written = 0; + while (static_cast<absl::string_view::size_type>(written) < contents.size()) { + int wrote = write(fd, contents.data() + written, contents.size() - written); + if (wrote < 0) { + if (errno == EINTR) { + continue; + } + return PosixError( + errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure.")); + } + written += wrote; + } + return NoError(); +} +} // namespace + +namespace internal { + +// Given a collection of file paths, append them all together, +// ensuring that the proper path separators are inserted between them. +std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) { + std::string result; + + if (paths.size() != 0) { + // This size calculation is worst-case: it assumes one extra "/" for every + // path other than the first. + size_t total_size = paths.size() - 1; + for (const absl::string_view path : paths) total_size += path.size(); + result.resize(total_size); + + auto begin = result.begin(); + auto out = begin; + bool trailing_slash = false; + for (absl::string_view path : paths) { + if (path.empty()) continue; + if (path.front() == '/') { + if (trailing_slash) { + path.remove_prefix(1); + } + } else { + if (!trailing_slash && out != begin) *out++ = '/'; + } + const size_t this_size = path.size(); + memcpy(&*out, path.data(), this_size); + out += this_size; + trailing_slash = out[-1] == '/'; + } + result.erase(out - begin); + } + return result; +} +} // namespace internal + +// Returns a status or the current working directory. +PosixErrorOr<std::string> GetCWD() { + char buffer[PATH_MAX + 1] = {}; + if (getcwd(buffer, PATH_MAX) == nullptr) { + return PosixError(errno, "GetCWD() failed"); + } + + return std::string(buffer); +} + +PosixErrorOr<struct stat> Stat(absl::string_view path) { + struct stat stat_buf; + int res = stat(std::string(path).c_str(), &stat_buf); + if (res < 0) { + return PosixError(errno, absl::StrCat("stat ", path)); + } + return stat_buf; +} + +PosixErrorOr<bool> Exists(absl::string_view path) { + struct stat stat_buf; + int res = stat(std::string(path).c_str(), &stat_buf); + if (res < 0) { + if (errno == ENOENT) { + return false; + } + return PosixError(errno, absl::StrCat("stat ", path)); + } + return true; +} + +PosixErrorOr<bool> IsDirectory(absl::string_view path) { + ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Stat(path)); + if (S_ISDIR(stat_buf.st_mode)) { + return true; + } + + return false; +} + +PosixError Delete(absl::string_view path) { + int res = unlink(std::string(path).c_str()); + if (res < 0) { + return PosixError(errno, absl::StrCat("unlink ", path)); + } + + return NoError(); +} + +PosixError Truncate(absl::string_view path, int length) { + int res = truncate(std::string(path).c_str(), length); + if (res < 0) { + return PosixError(errno, + absl::StrCat("truncate ", path, " to length ", length)); + } + + return NoError(); +} + +PosixError Chmod(absl::string_view path, int mode) { + int res = chmod(std::string(path).c_str(), mode); + if (res < 0) { + return PosixError(errno, absl::StrCat("chmod ", path)); + } + + return NoError(); +} + +PosixError Mkdir(absl::string_view path, int mode) { + int res = mkdir(std::string(path).c_str(), mode); + if (res < 0) { + return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode)); + } + + return NoError(); +} + +PosixError Rmdir(absl::string_view path) { + int res = rmdir(std::string(path).c_str()); + if (res < 0) { + return PosixError(errno, absl::StrCat("rmdir ", path)); + } + + return NoError(); +} + +PosixError SetContents(absl::string_view path, absl::string_view contents) { + ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); + if (!exists) { + return PosixError( + ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist.")); + } + + ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC)); + return WriteContentsToFD(fd.get(), contents); +} + +// Create a file with the given contents (if it does not already exist with the +// given mode) and then set the contents. +PosixError CreateWithContents(absl::string_view path, + absl::string_view contents, int mode) { + ASSIGN_OR_RETURN_ERRNO( + auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode)); + return WriteContentsToFD(fd.get(), contents); +} + +PosixError GetContents(absl::string_view path, std::string* output) { + ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY)); + output->clear(); + + // Keep reading until we hit an EOF or an error. + return GetContentsFD(fd.get(), output); +} + +PosixErrorOr<std::string> GetContents(absl::string_view path) { + std::string ret; + RETURN_IF_ERRNO(GetContents(path, &ret)); + return ret; +} + +PosixErrorOr<std::string> GetContentsFD(int fd) { + std::string ret; + RETURN_IF_ERRNO(GetContentsFD(fd, &ret)); + return ret; +} + +PosixError GetContentsFD(int fd, std::string* output) { + // Keep reading until we hit an EOF or an error. + while (true) { + char buf[16 * 1024] = {}; // Read in 16KB chunks. + int bytes_read = read(fd, buf, sizeof(buf)); + if (bytes_read < 0) { + if (errno == EINTR) { + continue; + } + return PosixError(errno, "GetContentsFD read failure."); + } + + if (bytes_read == 0) { + break; // EOF. + } + + output->append(buf, bytes_read); + } + return NoError(); +} + +PosixErrorOr<std::string> ReadLink(absl::string_view path) { + char buf[PATH_MAX + 1] = {}; + int ret = readlink(std::string(path).c_str(), buf, PATH_MAX); + if (ret < 0) { + return PosixError(errno, absl::StrCat("readlink ", path)); + } + + return std::string(buf, ret); +} + +PosixError WalkTree( + absl::string_view path, bool recursive, + const std::function<void(absl::string_view, const struct stat&)>& cb) { + DIR* dir = opendir(std::string(path).c_str()); + if (dir == nullptr) { + return PosixError(errno, absl::StrCat("opendir ", path)); + } + auto dir_closer = Cleanup([&dir]() { closedir(dir); }); + while (true) { + // Readdir(3): If the end of the directory stream is reached, NULL is + // returned and errno is not changed. If an error occurs, NULL is returned + // and errno is set appropriately. To distinguish end of stream and from an + // error, set errno to zero before calling readdir() and then check the + // value of errno if NULL is returned. + errno = 0; + struct dirent* dp = readdir(dir); + if (dp == nullptr) { + if (errno != 0) { + return PosixError(errno, absl::StrCat("readdir ", path)); + } + break; // We're done. + } + + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + // Skip dots. + continue; + } + + auto full_path = JoinPath(path, dp->d_name); + ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path)); + if (S_ISDIR(s.st_mode) && recursive) { + RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb)); + } else { + cb(full_path, s); + } + } + // We're done walking so let's invoke our cleanup callback now. + dir_closer.Release()(); + + // And we have to dispatch the callback on the base directory. + ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path)); + cb(path, s); + + return NoError(); +} + +PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, + bool skipdots) { + std::vector<std::string> files; + + DIR* dir = opendir(std::string(abspath).c_str()); + if (dir == nullptr) { + return PosixError(errno, absl::StrCat("opendir ", abspath)); + } + auto dir_closer = Cleanup([&dir]() { closedir(dir); }); + while (true) { + // Readdir(3): If the end of the directory stream is reached, NULL is + // returned and errno is not changed. If an error occurs, NULL is returned + // and errno is set appropriately. To distinguish end of stream and from an + // error, set errno to zero before calling readdir() and then check the + // value of errno if NULL is returned. + errno = 0; + struct dirent* dp = readdir(dir); + if (dp == nullptr) { + if (errno != 0) { + return PosixError(errno, absl::StrCat("readdir ", abspath)); + } + break; // We're done. + } + + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + if (skipdots) { + continue; + } + } + files.push_back(std::string(dp->d_name)); + } + + return files; +} + +PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, + int* undeleted_files) { + ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); + if (!exists) { + return PosixError(ENOENT, absl::StrCat(path, " does not exist")); + } + + ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path)); + if (!dir) { + // Nothing recursive needs to happen we can just call Delete. + auto status = Delete(path); + if (!status.ok() && undeleted_files) { + (*undeleted_files)++; + } + return status; + } + + return WalkTree(path, /*recursive=*/true, + [&](absl::string_view absolute_path, const struct stat& s) { + if (S_ISDIR(s.st_mode)) { + auto rm_status = Rmdir(absolute_path); + if (!rm_status.ok() && undeleted_dirs) { + (*undeleted_dirs)++; + } + } else { + auto delete_status = Delete(absolute_path); + if (!delete_status.ok() && undeleted_files) { + (*undeleted_files)++; + } + } + }); +} + +PosixError RecursivelyCreateDir(absl::string_view path) { + if (path.empty() || path == "/") { + return PosixError(EINVAL, "Cannot create root!"); + } + + // Does it already exist, if so we're done. + ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); + if (exists) { + return NoError(); + } + + // Do we need to create directories under us? + auto dirname = Dirname(path); + ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname)); + if (!exists) { + RETURN_IF_ERRNO(RecursivelyCreateDir(dirname)); + } + + return Mkdir(path); +} + +// Makes a path absolute with respect to an optional base. If no base is +// provided it will use the current working directory. +PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename, + absl::string_view base) { + if (filename.empty()) { + return PosixError(EINVAL, "filename cannot be empty."); + } + + if (filename[0] == '/') { + // This path is already absolute. + return std::string(filename); + } + + std::string actual_base; + if (!base.empty()) { + actual_base = std::string(base); + } else { + auto cwd_or = GetCWD(); + RETURN_IF_ERRNO(cwd_or.error()); + actual_base = cwd_or.ValueOrDie(); + } + + // Reverse iterate removing trailing slashes, effectively right trim '/'. + for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) { + actual_base.erase(i, 1); + } + + if (filename == ".") { + return actual_base.empty() ? "/" : actual_base; + } + + return absl::StrCat(actual_base, "/", filename); +} + +std::string CleanPath(const absl::string_view unclean_path) { + std::string path = std::string(unclean_path); + const char *src = path.c_str(); + std::string::iterator dst = path.begin(); + + // Check for absolute path and determine initial backtrack limit. + const bool is_absolute_path = *src == '/'; + if (is_absolute_path) { + *dst++ = *src++; + while (*src == '/') ++src; + } + std::string::const_iterator backtrack_limit = dst; + + // Process all parts + while (*src) { + bool parsed = false; + + if (src[0] == '.') { + // 1dot ".<whateverisnext>", check for END or SEP. + if (src[1] == '/' || !src[1]) { + if (*++src) { + ++src; + } + parsed = true; + } else if (src[1] == '.' && (src[2] == '/' || !src[2])) { + // 2dot END or SEP (".." | "../<whateverisnext>"). + src += 2; + if (dst != backtrack_limit) { + // We can backtrack the previous part + for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) { + // Empty. + } + } else if (!is_absolute_path) { + // Failed to backtrack and we can't skip it either. Rewind and copy. + src -= 2; + *dst++ = *src++; + *dst++ = *src++; + if (*src) { + *dst++ = *src; + } + // We can never backtrack over a copied "../" part so set new limit. + backtrack_limit = dst; + } + if (*src) { + ++src; + } + parsed = true; + } + } + + // If not parsed, copy entire part until the next SEP or EOS. + if (!parsed) { + while (*src && *src != '/') { + *dst++ = *src++; + } + if (*src) { + *dst++ = *src++; + } + } + + // Skip consecutive SEP occurrences + while (*src == '/') { + ++src; + } + } + + // Calculate and check the length of the cleaned path. + int path_length = dst - path.begin(); + if (path_length != 0) { + // Remove trailing '/' except if it is root path ("/" ==> path_length := 1) + if (path_length > 1 && path[path_length - 1] == '/') { + --path_length; + } + path.resize(path_length); + } else { + // The cleaned path is empty; assign "." as per the spec. + path.assign(1, '.'); + } + return path; +} + +PosixErrorOr<std::string> GetRelativePath(absl::string_view source, + absl::string_view dest) { + if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) { + // At least one of the inputs is not an absolute path. + return PosixError( + EINVAL, + "GetRelativePath: At least one of the inputs is not an absolute path."); + } + const std::string clean_source = CleanPath(source); + const std::string clean_dest = CleanPath(dest); + auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty()); + auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty()); + auto source_iter = source_parts.begin(); + auto dest_iter = dest_parts.begin(); + + // Advance past common prefix. + while (source_iter != source_parts.end() && dest_iter != dest_parts.end() && + *source_iter == *dest_iter) { + ++source_iter; + ++dest_iter; + } + + // Build result backtracking. + std::string result = ""; + while (source_iter != source_parts.end()) { + absl::StrAppend(&result, "../"); + ++source_iter; + } + + // Add remaining path to dest. + while (dest_iter != dest_parts.end()) { + absl::StrAppend(&result, *dest_iter, "/"); + ++dest_iter; + } + + if (result.empty()) { + return std::string("."); + } + + // Remove trailing slash. + result.erase(result.size() - 1); + return result; +} + +absl::string_view Dirname(absl::string_view path) { + return SplitPath(path).first; +} + +absl::string_view Basename(absl::string_view path) { + return SplitPath(path).second; +} + +std::pair<absl::string_view, absl::string_view> SplitPath( + absl::string_view path) { + std::string::size_type pos = path.find_last_of('/'); + + // Handle the case with no '/' in 'path'. + if (pos == absl::string_view::npos) + return std::make_pair(path.substr(0, 0), path); + + // Handle the case with a single leading '/' in 'path'. + if (pos == 0) + return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1)); + + return std::make_pair(path.substr(0, pos), + absl::ClippedSubstr(path, pos + 1)); +} + +std::string JoinPath(absl::string_view path1, absl::string_view path2) { + if (path1.empty()) return std::string(path2); + if (path2.empty()) return std::string(path1); + if (path1.back() == '/') { + if (path2.front() == '/') + return absl::StrCat(path1, absl::ClippedSubstr(path2, 1)); + } else { + if (path2.front() != '/') return absl::StrCat(path1, "/", path2); + } + return absl::StrCat(path1, path2); +} + +PosixErrorOr<std::string> ProcessExePath(int pid) { + if (pid <= 0) { + return PosixError(EINVAL, "Invalid pid specified"); + } + + return ReadLink(absl::StrCat("/proc/", pid, "/exe")); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/fs_util.h b/test/util/fs_util.h new file mode 100644 index 000000000..9412b2f71 --- /dev/null +++ b/test/util/fs_util.h @@ -0,0 +1,182 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_FS_UTIL_H_ +#define GVISOR_TEST_UTIL_FS_UTIL_H_ + +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "absl/strings/string_view.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { +// Returns a status or the current working directory. +PosixErrorOr<std::string> GetCWD(); + +// Returns true/false depending on whether or not path exists, or an error if it +// can't be determined. +PosixErrorOr<bool> Exists(absl::string_view path); + +// Returns a stat structure for the given path or an error. +PosixErrorOr<struct stat> Stat(absl::string_view path); + +// Deletes the file or directory at path or returns an error. +PosixError Delete(absl::string_view path); + +// Changes the mode of a file or returns an error. +PosixError Chmod(absl::string_view path, int mode); + +// Truncates a file to the given length or returns an error. +PosixError Truncate(absl::string_view path, int length); + +// Returns true/false depending on whether or not the path is a directory or +// returns an error. +PosixErrorOr<bool> IsDirectory(absl::string_view path); + +// Makes a directory or returns an error. +PosixError Mkdir(absl::string_view path, int mode = 0755); + +// Removes a directory or returns an error. +PosixError Rmdir(absl::string_view path); + +// Attempts to set the contents of a file or returns an error. +PosixError SetContents(absl::string_view path, absl::string_view contents); + +// Creates a file with the given contents and mode or returns an error. +PosixError CreateWithContents(absl::string_view path, + absl::string_view contents, int mode = 0666); + +// Attempts to read the entire contents of the file into the provided std::string +// buffer or returns an error. +PosixError GetContents(absl::string_view path, std::string* output); + +// Attempts to read the entire contents of the file or returns an error. +PosixErrorOr<std::string> GetContents(absl::string_view path); + +// Attempts to read the entire contents of the provided fd into the provided +// std::string or returns an error. +PosixError GetContentsFD(int fd, std::string* output); + +// Attempts to read the entire contents of the provided fd or returns an error. +PosixErrorOr<std::string> GetContentsFD(int fd); + +// Executes the readlink(2) system call or returns an error. +PosixErrorOr<std::string> ReadLink(absl::string_view path); + +// WalkTree will walk a directory tree in a depth first search manner (if +// recursive). It will invoke a provided callback for each file and directory, +// the parent will always be invoked last making this appropriate for things +// such as deleting an entire directory tree. +// +// This method will return an error when it's unable to access the provided +// path, or when the path is not a directory. +PosixError WalkTree( + absl::string_view path, bool recursive, + const std::function<void(absl::string_view, const struct stat&)>& cb); + +// Returns the base filenames for all files under a given absolute path. If +// skipdots is true the returned vector will not contain "." or "..". This +// method does not walk the tree recursively it only returns the elements +// in that directory. +PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, + bool skipdots); + +// Attempt to recursively delete a directory or file. Returns an error and +// the number of undeleted directories and files. If either +// undeleted_dirs or undeleted_files is nullptr then it will not be used. +PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, + int* undeleted_files); + +// Recursively create the directory provided or return an error. +PosixError RecursivelyCreateDir(absl::string_view path); + +// Makes a path absolute with respect to an optional base. If no base is +// provided it will use the current working directory. +PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename, + absl::string_view base); + +// Generates a relative path from the source directory to the destination +// (dest) file or directory. This uses ../ when necessary for destinations +// which are not nested within the source. Both source and dest are required +// to be absolute paths, and an empty std::string will be returned if they are not. +PosixErrorOr<std::string> GetRelativePath(absl::string_view source, + absl::string_view dest); + +// Returns the part of the path before the final "/", EXCEPT: +// * If there is a single leading "/" in the path, the result will be the +// leading "/". +// * If there is no "/" in the path, the result is the empty prefix of the +// input std::string. +absl::string_view Dirname(absl::string_view path); + +// Return the parts of the path, split on the final "/". If there is no +// "/" in the path, the first part of the output is empty and the second +// is the input. If the only "/" in the path is the first character, it is +// the first part of the output. +std::pair<absl::string_view, absl::string_view> SplitPath( + absl::string_view path); + +// Returns the part of the path after the final "/". If there is no +// "/" in the path, the result is the same as the input. +// Note that this function's behavior differs from the Unix basename +// command if path ends with "/". For such paths, this function returns the +// empty std::string. +absl::string_view Basename(absl::string_view path); + +// Collapse duplicate "/"s, resolve ".." and "." path elements, remove +// trailing "/". +// +// NOTE: This respects relative vs. absolute paths, but does not +// invoke any system calls (getcwd(2)) in order to resolve relative +// paths wrt actual working directory. That is, this is purely a +// std::string manipulation, completely independent of process state. +std::string CleanPath(absl::string_view path); + +// Returns the full path to the executable of the given pid or a PosixError. +PosixErrorOr<std::string> ProcessExePath(int pid); + +namespace internal { +// Not part of the public API. +std::string JoinPathImpl(std::initializer_list<absl::string_view> paths); +} // namespace internal + +// Join multiple paths together. +// All paths will be treated as relative paths, regardless of whether or not +// they start with a leading '/'. That is, all paths will be concatenated +// together, with the appropriate path separator inserted in between. +// Arguments must be convertible to absl::string_view. +// +// Usage: +// std::string path = JoinPath("/foo", dirname, filename); +// std::string path = JoinPath(FLAGS_test_srcdir, filename); +// +// 0, 1, 2-path specializations exist to optimize common cases. +inline std::string JoinPath() { return std::string(); } +inline std::string JoinPath(absl::string_view path) { + return std::string(path.data(), path.size()); +} + +std::string JoinPath(absl::string_view path1, absl::string_view path2); +template <typename... T> +inline std::string JoinPath(absl::string_view path1, absl::string_view path2, + absl::string_view path3, const T&... args) { + return internal::JoinPathImpl({path1, path2, path3, args...}); +} +} // namespace testing +} // namespace gvisor +#endif // GVISOR_TEST_UTIL_FS_UTIL_H_ diff --git a/test/util/fs_util_test.cc b/test/util/fs_util_test.cc new file mode 100644 index 000000000..ce70d58aa --- /dev/null +++ b/test/util/fs_util_test.cc @@ -0,0 +1,100 @@ +// Copyright 2018 Google LLC +// +// 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 <errno.h> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/util/fs_util.h" +#include "test/util/posix_error.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +TEST(FsUtilTest, RecursivelyCreateDirManualDelete) { + const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string base_path = JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m"); + + ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false)); + ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path)); + + // Delete everything until we hit root and then stop, we want to try this + // without using RecursivelyDelete. + std::string cur_path = base_path; + while (cur_path != root.path()) { + ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(true)); + ASSERT_NO_ERRNO(Rmdir(cur_path)); + ASSERT_THAT(Exists(cur_path), IsPosixErrorOkAndHolds(false)); + auto dir = Dirname(cur_path); + cur_path = std::string(dir); + } +} + +TEST(FsUtilTest, RecursivelyCreateAndDeleteDir) { + const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string base_path = JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m"); + + ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false)); + ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path)); + + const std::string sub_path = JoinPath(root.path(), "a"); + ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr)); + ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false)); +} + +TEST(FsUtilTest, RecursivelyCreateAndDeletePartial) { + const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + const std::string base_path = JoinPath(root.path(), "/a/b/c/d/e/f/g/h/i/j/k/l/m"); + + ASSERT_THAT(Exists(base_path), IsPosixErrorOkAndHolds(false)); + ASSERT_NO_ERRNO(RecursivelyCreateDir(base_path)); + + const std::string a = JoinPath(root.path(), "a"); + auto listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, true)); + ASSERT_THAT(listing, ::testing::Contains("b")); + ASSERT_EQ(listing.size(), 1); + + listing = ASSERT_NO_ERRNO_AND_VALUE(ListDir(a, false)); + ASSERT_THAT(listing, ::testing::Contains(".")); + ASSERT_THAT(listing, ::testing::Contains("..")); + ASSERT_THAT(listing, ::testing::Contains("b")); + ASSERT_EQ(listing.size(), 3); + + const std::string sub_path = JoinPath(root.path(), "/a/b/c/d/e/f"); + + ASSERT_NO_ERRNO( + CreateWithContents(JoinPath(Dirname(sub_path), "file"), "Hello World")); + std::string contents = ""; + ASSERT_NO_ERRNO(GetContents(JoinPath(Dirname(sub_path), "file"), &contents)); + ASSERT_EQ(contents, "Hello World"); + + ASSERT_NO_ERRNO(RecursivelyDelete(sub_path, nullptr, nullptr)); + ASSERT_THAT(Exists(sub_path), IsPosixErrorOkAndHolds(false)); + + // The parent of the subpath (directory e) should still exist. + ASSERT_THAT(Exists(Dirname(sub_path)), IsPosixErrorOkAndHolds(true)); + + // The file we created along side f should also still exist. + ASSERT_THAT(Exists(JoinPath(Dirname(sub_path), "file")), + IsPosixErrorOkAndHolds(true)); +} +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/logging.cc b/test/util/logging.cc new file mode 100644 index 000000000..86ea71df3 --- /dev/null +++ b/test/util/logging.cc @@ -0,0 +1,97 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/logging.h" + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +namespace gvisor { +namespace testing { + +namespace { + +// We implement this here instead of using test_util to avoid cyclic +// dependencies. +int Write(int fd, const char* buf, size_t size) { + size_t written = 0; + while (written < size) { + int res = write(fd, buf + written, size - written); + if (res < 0 && errno == EINTR) { + continue; + } else if (res <= 0) { + break; + } + + written += res; + } + return static_cast<int>(written); +} + +// Write 32-bit decimal number to fd. +int WriteNumber(int fd, uint32_t val) { + constexpr char kDigits[] = "0123456789"; + constexpr int kBase = 10; + + // 10 chars for 32-bit number in decimal, 1 char for the NUL-terminator. + constexpr int kBufferSize = 11; + char buf[kBufferSize]; + + // Convert the number to std::string. + char* s = buf + sizeof(buf) - 1; + size_t size = 0; + + *s = '\0'; + do { + s--; + size++; + + *s = kDigits[val % kBase]; + val /= kBase; + } while (val); + + return Write(fd, s, size); +} + +} // namespace + +void CheckFailure(const char* cond, size_t cond_size, const char* msg, + size_t msg_size, bool include_errno) { + int saved_errno = errno; + + constexpr char kCheckFailure[] = "Check failed: "; + Write(2, kCheckFailure, sizeof(kCheckFailure) - 1); + Write(2, cond, cond_size); + + if (msg != nullptr) { + Write(2, ": ", 2); + Write(2, msg, msg_size); + } + + if (include_errno) { + constexpr char kErrnoMessage[] = " (errno "; + Write(2, kErrnoMessage, sizeof(kErrnoMessage) - 1); + WriteNumber(2, saved_errno); + Write(2, ")", 1); + } + + Write(2, "\n", 1); + + abort(); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/logging.h b/test/util/logging.h new file mode 100644 index 000000000..6e957b172 --- /dev/null +++ b/test/util/logging.h @@ -0,0 +1,73 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_LOGGING_H_ +#define GVISOR_TEST_UTIL_LOGGING_H_ + +#include <stddef.h> + +namespace gvisor { +namespace testing { + +void CheckFailure(const char* cond, size_t cond_size, const char* msg, + size_t msg_size, bool include_errno); + +// If cond is false, aborts the current process. +// +// This macro is async-signal-safe. +#define TEST_CHECK(cond) \ + do { \ + if (!(cond)) { \ + ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \ + 0, false); \ + } \ + } while (0) + +// If cond is false, logs msg then aborts the current process. +// +// This macro is async-signal-safe. +#define TEST_CHECK_MSG(cond, msg) \ + do { \ + if (!(cond)) { \ + ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \ + sizeof(msg) - 1, false); \ + } \ + } while (0) + +// If cond is false, logs errno, then aborts the current process. +// +// This macro is async-signal-safe. +#define TEST_PCHECK(cond) \ + do { \ + if (!(cond)) { \ + ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, nullptr, \ + 0, true); \ + } \ + } while (0) + +// If cond is false, logs msg and errno, then aborts the current process. +// +// This macro is async-signal-safe. +#define TEST_PCHECK_MSG(cond, msg) \ + do { \ + if (!(cond)) { \ + ::gvisor::testing::CheckFailure(#cond, sizeof(#cond) - 1, msg, \ + sizeof(msg) - 1, true); \ + } \ + } while (0) + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_LOGGING_H_ diff --git a/test/util/memory_util.h b/test/util/memory_util.h new file mode 100644 index 000000000..8f6e99ba6 --- /dev/null +++ b/test/util/memory_util.h @@ -0,0 +1,124 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_MEMORY_UTIL_H_ +#define GVISOR_TEST_UTIL_MEMORY_UTIL_H_ + +#include <errno.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/mman.h> + +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "test/util/logging.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// RAII type for mmap'ed memory. Only usable in tests due to use of a test-only +// macro that can't be named without invoking the presubmit's wrath. +class Mapping { + public: + // Constructs a mapping that owns nothing. + Mapping() = default; + + // Constructs a mapping that owns the mmapped memory [ptr, ptr+len). Most + // users should use Mmap or MmapAnon instead. + Mapping(void* ptr, size_t len) : ptr_(ptr), len_(len) {} + + Mapping(Mapping&& orig) : ptr_(orig.ptr_), len_(orig.len_) { orig.release(); } + + Mapping& operator=(Mapping&& orig) { + ptr_ = orig.ptr_; + len_ = orig.len_; + orig.release(); + return *this; + } + + Mapping(Mapping const&) = delete; + Mapping& operator=(Mapping const&) = delete; + + ~Mapping() { reset(); } + + void* ptr() const { return ptr_; } + size_t len() const { return len_; } + + // Returns a pointer to the end of the mapping. Useful for when the mapping + // is used as a thread stack. + void* endptr() const { return reinterpret_cast<void*>(addr() + len_); } + + // Returns the start of this mapping cast to uintptr_t for ease of pointer + // arithmetic. + uintptr_t addr() const { return reinterpret_cast<uintptr_t>(ptr_); } + + // Returns the end of this mapping cast to uintptr_t for ease of pointer + // arithmetic. + uintptr_t endaddr() const { return reinterpret_cast<uintptr_t>(endptr()); } + + // Returns this mapping as a StringPiece for ease of comparison. + // + // This function is named view in anticipation of the eventual replacement of + // StringPiece with std::string_view. + absl::string_view view() const { + return absl::string_view(static_cast<char const*>(ptr_), len_); + } + + // These are both named reset for consistency with standard smart pointers. + + void reset(void* ptr, size_t len) { + if (len_) { + TEST_PCHECK(munmap(ptr_, len_) == 0); + } + ptr_ = ptr; + len_ = len; + } + + void reset() { reset(nullptr, 0); } + + void release() { + ptr_ = nullptr; + len_ = 0; + } + + private: + void* ptr_ = nullptr; + size_t len_ = 0; +}; + +// Wrapper around mmap(2) that returns a Mapping. +inline PosixErrorOr<Mapping> Mmap(void* addr, size_t length, int prot, + int flags, int fd, off_t offset) { + void* ptr = mmap(addr, length, prot, flags, fd, offset); + if (ptr == MAP_FAILED) { + return PosixError( + errno, absl::StrFormat("mmap(%p, %d, %x, %x, %d, %d)", addr, length, + prot, flags, fd, offset)); + } + MaybeSave(); + return Mapping(ptr, length); +} + +// Convenience wrapper around Mmap for anonymous mappings. +inline PosixErrorOr<Mapping> MmapAnon(size_t length, int prot, int flags) { + return Mmap(nullptr, length, prot, flags | MAP_ANONYMOUS, -1, 0); +} + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_MEMORY_UTIL_H_ diff --git a/test/util/mount_util.h b/test/util/mount_util.h new file mode 100644 index 000000000..468170646 --- /dev/null +++ b/test/util/mount_util.h @@ -0,0 +1,48 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_MOUNT_UTIL_H_ +#define GVISOR_TEST_UTIL_MOUNT_UTIL_H_ + +#include <errno.h> +#include <sys/mount.h> +#include <functional> +#include <string> + +#include "gmock/gmock.h" +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Mount mounts the filesystem, and unmounts when the returned reference is +// destroyed. +inline PosixErrorOr<Cleanup> Mount(const std::string &source, const std::string &target, + const std::string &fstype, uint64_t mountflags, + const std::string &data, uint64_t umountflags) { + if (mount(source.c_str(), target.c_str(), fstype.c_str(), mountflags, + data.c_str()) == -1) { + return PosixError(errno, "mount failed"); + } + return Cleanup([target, umountflags]() { + EXPECT_THAT(umount2(target.c_str(), umountflags), SyscallSucceeds()); + }); +} + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_MOUNT_UTIL_H_ diff --git a/test/util/multiprocess_util.cc b/test/util/multiprocess_util.cc new file mode 100644 index 000000000..12637db8c --- /dev/null +++ b/test/util/multiprocess_util.cc @@ -0,0 +1,139 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/multiprocess_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/prctl.h> +#include <unistd.h> + +#include "absl/strings/str_cat.h" +#include "test/util/cleanup.h" +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, + const ExecveArray& argv, + const ExecveArray& envv, + const std::function<void()>& fn, pid_t* child, + int* execve_errno) { + int pfds[2]; + int ret = pipe2(pfds, O_CLOEXEC); + if (ret < 0) { + return PosixError(errno, "pipe failed"); + } + FileDescriptor rfd(pfds[0]); + FileDescriptor wfd(pfds[1]); + + int parent_stdout = dup(STDOUT_FILENO); + if (parent_stdout < 0) { + return PosixError(errno, "dup stdout"); + } + int parent_stderr = dup(STDERR_FILENO); + if (parent_stdout < 0) { + return PosixError(errno, "dup stderr"); + } + + pid_t pid = fork(); + if (pid < 0) { + return PosixError(errno, "fork failed"); + } else if (pid == 0) { + // Child. + rfd.reset(); + if (dup2(parent_stdout, STDOUT_FILENO) < 0) { + _exit(3); + } + if (dup2(parent_stderr, STDERR_FILENO) < 0) { + _exit(4); + } + close(parent_stdout); + close(parent_stderr); + + // Clean ourself up in case the parent doesn't. + if (prctl(PR_SET_PDEATHSIG, SIGKILL)) { + _exit(3); + } + + if (fn) { + fn(); + } + + execve(filename.c_str(), argv.get(), envv.get()); + int error = errno; + if (WriteFd(pfds[1], &error, sizeof(error)) != sizeof(error)) { + // We can't do much if the write fails, but we can at least exit with a + // different code. + _exit(2); + } + _exit(1); + } + + // Parent. + if (child) { + *child = pid; + } + + auto cleanup = Cleanup([pid] { + kill(pid, SIGKILL); + RetryEINTR(waitpid)(pid, nullptr, 0); + }); + + wfd.reset(); + + int read_errno; + ret = ReadFd(rfd.get(), &read_errno, sizeof(read_errno)); + if (ret == 0) { + // Other end of the pipe closed, execve must have succeeded. + read_errno = 0; + } else if (ret < 0) { + return PosixError(errno, "read pipe failed"); + } else if (ret != sizeof(read_errno)) { + return PosixError(EPIPE, absl::StrCat("pipe read wrong size ", ret)); + } + + if (execve_errno) { + *execve_errno = read_errno; + } + + return std::move(cleanup); +} + +PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn) { + pid_t pid = fork(); + if (pid == 0) { + fn(); + _exit(0); + } + MaybeSave(); + if (pid < 0) { + return PosixError(errno, "fork failed"); + } + + int status; + if (waitpid(pid, &status, 0) < 0) { + return PosixError(errno, "waitpid failed"); + } + + return status; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/multiprocess_util.h b/test/util/multiprocess_util.h new file mode 100644 index 000000000..c09d6167f --- /dev/null +++ b/test/util/multiprocess_util.h @@ -0,0 +1,113 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_ +#define GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_ + +#include <unistd.h> + +#include <algorithm> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// Immutable holder for a dynamically-sized array of pointers to mutable char, +// terminated by a null pointer, as required for the argv and envp arguments to +// execve(2). +class ExecveArray { + public: + // Constructs an empty ExecveArray. + ExecveArray() = default; + + // Constructs an ExecveArray by copying strings from the given range. T must + // be a range over ranges of char. + template <typename T> + explicit ExecveArray(T const& strs) : ExecveArray(strs.begin(), strs.end()) {} + + // Constructs an ExecveArray by copying strings from [first, last). InputIt + // must be an input iterator over a range over char. + template <typename InputIt> + ExecveArray(InputIt first, InputIt last) { + std::vector<size_t> offsets; + auto output_it = std::back_inserter(str_); + for (InputIt it = first; it != last; ++it) { + offsets.push_back(str_.size()); + auto const& s = *it; + std::copy(s.begin(), s.end(), output_it); + str_.push_back('\0'); + } + ptrs_.reserve(offsets.size() + 1); + for (auto offset : offsets) { + ptrs_.push_back(str_.data() + offset); + } + ptrs_.push_back(nullptr); + } + + // Constructs an ExecveArray by copying strings from list. This overload must + // exist independently of the single-argument template constructor because + // std::initializer_list does not participate in template argument deduction + // (i.e. cannot be type-inferred in an invocation of the templated + // constructor). + /* implicit */ ExecveArray(std::initializer_list<absl::string_view> list) + : ExecveArray(list.begin(), list.end()) {} + + // Disable move construction and assignment since ptrs_ points into str_. + ExecveArray(ExecveArray&&) = delete; + ExecveArray& operator=(ExecveArray&&) = delete; + + char* const* get() const { return ptrs_.data(); } + + private: + std::vector<char> str_; + std::vector<char*> ptrs_; +}; + +// Simplified version of SubProcess. Returns OK and a cleanup function to kill +// the child if it made it to execve. +// +// fn is run between fork and exec. If it needs to fail, it should exit the +// process. +// +// The child pid is returned via child, if provided. +// execve's error code is returned via execve_errno, if provided. +PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, + const ExecveArray& argv, + const ExecveArray& envv, + const std::function<void()>& fn, pid_t* child, + int* execve_errno); + +inline PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, + const ExecveArray& argv, + const ExecveArray& envv, pid_t* child, + int* execve_errno) { + return ForkAndExec(filename, argv, envv, [] {}, child, execve_errno); +} + +// Calls fn in a forked subprocess and returns the exit status of the +// subprocess. +// +// fn must be async-signal-safe. +PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_MULTIPROCESS_UTIL_H_ diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc new file mode 100644 index 000000000..9db72c6de --- /dev/null +++ b/test/util/posix_error.cc @@ -0,0 +1,93 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/posix_error.h" + +#include <cassert> +#include <cerrno> +#include <cstring> +#include <string> + +#include "absl/strings/str_cat.h" + +namespace gvisor { +namespace testing { + +std::string PosixError::ToString() const { + if (ok()) { + return "No Error"; + } + + std::string ret; + + char strerrno_buf[1024] = {}; + char* msg = nullptr; + if ((msg = strerror_r(errno_, strerrno_buf, sizeof(strerrno_buf))) == + nullptr) { + ret = absl::StrCat("PosixError(errno=", errno_, " strerror_r FAILED)"); + } else { + ret = absl::StrCat("PosixError(errno=", errno_, " ", msg, ")"); + } + + if (!msg_.empty()) { + ret.append(" "); + ret.append(msg_); + } + + return ret; +} + +::std::ostream& operator<<(::std::ostream& os, const PosixError& e) { + os << e.ToString(); + return os; +} + +void PosixErrorIsMatcherCommonImpl::DescribeTo(std::ostream* os) const { + *os << "has an errno value that "; + code_matcher_.DescribeTo(os); + *os << ", and has an error message that "; + message_matcher_.DescribeTo(os); +} + +void PosixErrorIsMatcherCommonImpl::DescribeNegationTo(std::ostream* os) const { + *os << "has an errno value that "; + code_matcher_.DescribeNegationTo(os); + *os << ", or has an error message that "; + message_matcher_.DescribeNegationTo(os); +} + +bool PosixErrorIsMatcherCommonImpl::MatchAndExplain( + const PosixError& error, + ::testing::MatchResultListener* result_listener) const { + ::testing::StringMatchResultListener inner_listener; + + inner_listener.Clear(); + if (!code_matcher_.MatchAndExplain(error.errno_value(), &inner_listener)) { + *result_listener << (inner_listener.str().empty() + ? "whose errno value is wrong" + : "which has a errno value " + + inner_listener.str()); + return false; + } + + if (!message_matcher_.Matches(error.error_message())) { + *result_listener << "whose error message is wrong"; + return false; + } + + return true; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/posix_error.h b/test/util/posix_error.h new file mode 100644 index 000000000..8450be9b9 --- /dev/null +++ b/test/util/posix_error.h @@ -0,0 +1,428 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_POSIX_ERROR_H_ +#define GVISOR_TEST_UTIL_POSIX_ERROR_H_ + +#include <string> + +#include "gmock/gmock.h" +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" +#include "absl/types/variant.h" +#include "test/util/logging.h" + +namespace gvisor { +namespace testing { + +class PosixErrorIsMatcherCommonImpl; + +template <typename T> +class PosixErrorOr; + +class ABSL_MUST_USE_RESULT PosixError { + public: + PosixError() {} + explicit PosixError(int errno_value) : errno_(errno_value) {} + PosixError(int errno_value, std::string msg) + : errno_(errno_value), msg_(std::move(msg)) {} + + PosixError(PosixError&& other) = default; + PosixError& operator=(PosixError&& other) = default; + PosixError(const PosixError&) = default; + PosixError& operator=(const PosixError&) = default; + + bool ok() const { return errno_ == 0; } + + // Returns a reference to *this to make matchers compatible with + // PosixErrorOr. + const PosixError& error() const { return *this; } + + std::string error_message() const { return msg_; } + + // ToString produces a full std::string representation of this posix error + // including the printable representation of the errno and the error message. + std::string ToString() const; + + // Ignores any errors. This method does nothing except potentially suppress + // complaints from any tools that are checking that errors are not dropped on + // the floor. + void IgnoreError() const {} + + private: + int errno_value() const { return errno_; } + int errno_ = 0; + std::string msg_; + + friend class PosixErrorIsMatcherCommonImpl; + + template <typename T> + friend class PosixErrorOr; +}; + +template <typename T> +class ABSL_MUST_USE_RESULT PosixErrorOr { + public: + PosixErrorOr(const PosixError& error); // NOLINT + explicit PosixErrorOr(const T& value); + PosixErrorOr(T&& value); // NOLINT + + PosixErrorOr(PosixErrorOr&& other) = default; + PosixErrorOr& operator=(PosixErrorOr&& other) = default; + PosixErrorOr(const PosixErrorOr&) = default; + PosixErrorOr& operator=(const PosixErrorOr&) = default; + + // Conversion copy/move constructor, T must be convertible from U. + template <typename U> + friend class PosixErrorOr; + + template <typename U> + PosixErrorOr(PosixErrorOr<U> other); + + template <typename U> + PosixErrorOr& operator=(PosixErrorOr<U> other); + + // Return a reference to the error or NoError(). + const PosixError error() const; + + // Returns this->error().error_message(); + const std::string error_message() const; + + // Returns this->error().ok() + bool ok() const; + + // Returns a reference to our current value, or CHECK-fails if !this->ok(). + const T& ValueOrDie() const; + T& ValueOrDie(); + + // Ignores any errors. This method does nothing except potentially suppress + // complaints from any tools that are checking that errors are not dropped on + // the floor. + void IgnoreError() const {} + + private: + const int errno_value() const; + absl::variant<T, PosixError> value_; + + friend class PosixErrorIsMatcherCommonImpl; +}; + +template <typename T> +PosixErrorOr<T>::PosixErrorOr(const PosixError& error) : value_(error) {} + +template <typename T> +PosixErrorOr<T>::PosixErrorOr(const T& value) : value_(value) {} + +template <typename T> +PosixErrorOr<T>::PosixErrorOr(T&& value) : value_(std::move(value)) {} + +// Conversion copy/move constructor, T must be convertible from U. +template <typename T> +template <typename U> +inline PosixErrorOr<T>::PosixErrorOr(PosixErrorOr<U> other) { + if (absl::holds_alternative<U>(other.value_)) { + // T is convertible from U. + value_ = absl::get<U>(std::move(other.value_)); + } else if (absl::holds_alternative<PosixError>(other.value_)) { + value_ = absl::get<PosixError>(std::move(other.value_)); + } else { + TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value"); + } +} + +template <typename T> +template <typename U> +inline PosixErrorOr<T>& PosixErrorOr<T>::operator=(PosixErrorOr<U> other) { + if (absl::holds_alternative<U>(other.value_)) { + // T is convertible from U. + value_ = absl::get<U>(std::move(other.value_)); + } else if (absl::holds_alternative<PosixError>(other.value_)) { + value_ = absl::get<PosixError>(std::move(other.value_)); + } else { + TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value"); + } + return *this; +} + +template <typename T> +const PosixError PosixErrorOr<T>::error() const { + if (!absl::holds_alternative<PosixError>(value_)) { + return PosixError(); + } + return absl::get<PosixError>(value_); +} + +template <typename T> +const int PosixErrorOr<T>::errno_value() const { + return error().errno_value(); +} + +template <typename T> +const std::string PosixErrorOr<T>::error_message() const { + return error().error_message(); +} + +template <typename T> +bool PosixErrorOr<T>::ok() const { + return error().ok(); +} + +template <typename T> +const T& PosixErrorOr<T>::ValueOrDie() const { + TEST_CHECK(absl::holds_alternative<T>(value_)); + return absl::get<T>(value_); +} + +template <typename T> +T& PosixErrorOr<T>::ValueOrDie() { + TEST_CHECK(absl::holds_alternative<T>(value_)); + return absl::get<T>(value_); +} + +extern ::std::ostream& operator<<(::std::ostream& os, const PosixError& e); + +template <typename T> +::std::ostream& operator<<(::std::ostream& os, const PosixErrorOr<T>& e) { + os << e.error(); + return os; +} + +// NoError is a PosixError that represents a successful state, i.e. No Error. +inline PosixError NoError() { return PosixError(); } + +// Monomorphic implementation of matcher IsPosixErrorOk() for a given type T. +// T can be PosixError, PosixErrorOr<>, or a reference to either of them. +template <typename T> +class MonoPosixErrorIsOkMatcherImpl : public ::testing::MatcherInterface<T> { + public: + void DescribeTo(std::ostream* os) const override { *os << "is OK"; } + void DescribeNegationTo(std::ostream* os) const override { + *os << "is not OK"; + } + bool MatchAndExplain(T actual_value, + ::testing::MatchResultListener*) const override { + return actual_value.ok(); + } +}; + +// Implements IsPosixErrorOkMatcher() as a polymorphic matcher. +class IsPosixErrorOkMatcher { + public: + template <typename T> + operator ::testing::Matcher<T>() const { // NOLINT + return MakeMatcher(new MonoPosixErrorIsOkMatcherImpl<T>()); + } +}; + +// Monomorphic implementation of a matcher for a PosixErrorOr. +template <typename PosixErrorOrType> +class IsPosixErrorOkAndHoldsMatcherImpl + : public ::testing::MatcherInterface<PosixErrorOrType> { + public: + using ValueType = typename std::remove_reference<decltype( + std::declval<PosixErrorOrType>().ValueOrDie())>::type; + + template <typename InnerMatcher> + explicit IsPosixErrorOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher) + : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>( + std::forward<InnerMatcher>(inner_matcher))) {} + + void DescribeTo(std::ostream* os) const override { + *os << "is OK and has a value that "; + inner_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "isn't OK or has a value that "; + inner_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain( + PosixErrorOrType actual_value, + ::testing::MatchResultListener* listener) const override { + if (!actual_value.ok()) { + *listener << "which has error value " << actual_value.error(); + return false; + } + + ::testing::StringMatchResultListener inner_listener; + const bool matches = inner_matcher_.MatchAndExplain( + actual_value.ValueOrDie(), &inner_listener); + const std::string inner_explanation = inner_listener.str(); + if (!inner_explanation.empty()) { + *listener << "which contains value " + << ::testing::PrintToString(actual_value.ValueOrDie()) << ", " + << inner_explanation; + } + return matches; + } + + private: + const ::testing::Matcher<const ValueType&> inner_matcher_; +}; + +// Implements IsOkAndHolds() as a polymorphic matcher. +template <typename InnerMatcher> +class IsPosixErrorOkAndHoldsMatcher { + public: + explicit IsPosixErrorOkAndHoldsMatcher(InnerMatcher inner_matcher) + : inner_matcher_(std::move(inner_matcher)) {} + + // Converts this polymorphic matcher to a monomorphic one of the given type. + // PosixErrorOrType can be either PosixErrorOr<T> or a reference to + // PosixErrorOr<T>. + template <typename PosixErrorOrType> + operator ::testing::Matcher<PosixErrorOrType>() const { // NOLINT + return ::testing::MakeMatcher( + new IsPosixErrorOkAndHoldsMatcherImpl<PosixErrorOrType>( + inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; +}; + +// PosixErrorIs() is a polymorphic matcher. This class is the common +// implementation of it shared by all types T where PosixErrorIs() can be +// used as a Matcher<T>. +class PosixErrorIsMatcherCommonImpl { + public: + PosixErrorIsMatcherCommonImpl( + ::testing::Matcher<int> code_matcher, + ::testing::Matcher<const std::string&> message_matcher) + : code_matcher_(std::move(code_matcher)), + message_matcher_(std::move(message_matcher)) {} + + void DescribeTo(std::ostream* os) const; + + void DescribeNegationTo(std::ostream* os) const; + + bool MatchAndExplain(const PosixError& error, + ::testing::MatchResultListener* result_listener) const; + + private: + const ::testing::Matcher<int> code_matcher_; + const ::testing::Matcher<const std::string&> message_matcher_; +}; + +// Monomorphic implementation of matcher PosixErrorIs() for a given type +// T. T can be PosixError, PosixErrorOr<>, or a reference to either of them. +template <typename T> +class MonoPosixErrorIsMatcherImpl : public ::testing::MatcherInterface<T> { + public: + explicit MonoPosixErrorIsMatcherImpl( + PosixErrorIsMatcherCommonImpl common_impl) + : common_impl_(std::move(common_impl)) {} + + void DescribeTo(std::ostream* os) const override { + common_impl_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + common_impl_.DescribeNegationTo(os); + } + + bool MatchAndExplain( + T actual_value, + ::testing::MatchResultListener* result_listener) const override { + return common_impl_.MatchAndExplain(actual_value.error(), result_listener); + } + + private: + PosixErrorIsMatcherCommonImpl common_impl_; +}; + +inline ::testing::Matcher<int> ToErrorCodeMatcher( + const ::testing::Matcher<int>& m) { + return m; +} + +// Implements PosixErrorIs() as a polymorphic matcher. +class PosixErrorIsMatcher { + public: + template <typename ErrorCodeMatcher> + PosixErrorIsMatcher(ErrorCodeMatcher&& code_matcher, + ::testing::Matcher<const std::string&> message_matcher) + : common_impl_( + ToErrorCodeMatcher(std::forward<ErrorCodeMatcher>(code_matcher)), + std::move(message_matcher)) {} + + // Converts this polymorphic matcher to a monomorphic matcher of the + // given type. T can be StatusOr<>, Status, or a reference to + // either of them. + template <typename T> + operator ::testing::Matcher<T>() const { // NOLINT + return MakeMatcher(new MonoPosixErrorIsMatcherImpl<T>(common_impl_)); + } + + private: + const PosixErrorIsMatcherCommonImpl common_impl_; +}; + +// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose +// whose error code matches code_matcher, and whose error message matches +// message_matcher. +template <typename ErrorCodeMatcher> +PosixErrorIsMatcher PosixErrorIs( + ErrorCodeMatcher&& code_matcher, + ::testing::Matcher<const std::string&> message_matcher) { + return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher), + std::move(message_matcher)); +} + +// Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and +// value matches the inner matcher. +template <typename InnerMatcher> +IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type> +IsPosixErrorOkAndHolds(InnerMatcher&& inner_matcher) { + return IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>( + std::forward<InnerMatcher>(inner_matcher)); +} + +// Internal helper for concatenating macro values. +#define POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) x##y +#define POSIX_ERROR_IMPL_CONCAT_(x, y) POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) + +#define POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_(posixerroror, lhs, rexpr) \ + auto posixerroror = (rexpr); \ + if (!posixerroror.ok()) { \ + return (posixerroror.error()); \ + } \ + lhs = std::move(posixerroror.ValueOrDie()) + +#define EXPECT_NO_ERRNO(expression) \ + EXPECT_THAT(expression, IsPosixErrorOkMatcher()) +#define ASSERT_NO_ERRNO(expression) \ + ASSERT_THAT(expression, IsPosixErrorOkMatcher()) + +#define ASSIGN_OR_RETURN_ERRNO(lhs, rexpr) \ + POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_( \ + POSIX_ERROR_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr) + +#define RETURN_IF_ERRNO(s) \ + do { \ + if (!s.ok()) return s; \ + } while (false); + +#define ASSERT_NO_ERRNO_AND_VALUE(expr) \ + ({ \ + auto _expr_result = (expr); \ + ASSERT_NO_ERRNO(_expr_result); \ + std::move(_expr_result.ValueOrDie()); \ + }) + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_POSIX_ERROR_H_ diff --git a/test/util/posix_error_test.cc b/test/util/posix_error_test.cc new file mode 100644 index 000000000..535b9f66a --- /dev/null +++ b/test/util/posix_error_test.cc @@ -0,0 +1,45 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/posix_error.h" + +#include <errno.h> +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace gvisor { +namespace testing { + +namespace { + +TEST(PosixErrorTest, PosixError) { + auto err = PosixError(EAGAIN); + EXPECT_THAT(err, PosixErrorIs(EAGAIN, "")); +} + +TEST(PosixErrorTest, PosixErrorOrPosixError) { + auto err = PosixErrorOr<std::nullptr_t>(PosixError(EAGAIN)); + EXPECT_THAT(err, PosixErrorIs(EAGAIN, "")); +} + +TEST(PosixErrorTest, PosixErrorOrNullptr) { + auto err = PosixErrorOr<std::nullptr_t>(nullptr); + EXPECT_THAT(err, PosixErrorIs(0, "")); + EXPECT_NO_ERRNO(err); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/proc_util.cc b/test/util/proc_util.cc new file mode 100644 index 000000000..72f7e67d0 --- /dev/null +++ b/test/util/proc_util.cc @@ -0,0 +1,98 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/proc_util.h" + +#include <algorithm> +#include <vector> + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "test/util/fs_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Parses a single line from /proc/<xxx>/maps. +PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line) { + ProcMapsEntry map_entry = {}; + std::vector<std::string> parts = absl::StrSplit(line, ' ', absl::SkipEmpty()); + + // A size of 5 means there is no file name specified. + if (parts.size() != 5 && parts.size() != 6) { + return PosixError(EINVAL, absl::StrCat("Invalid line: ", line)); + } + + // Address range in the form X-X where X are hex values without leading 0x. + std::vector<std::string> addresses = absl::StrSplit(parts[0], '-'); + if (addresses.size() != 2) { + return PosixError(EINVAL, + absl::StrCat("Invalid address range: ", parts[0])); + } + ASSIGN_OR_RETURN_ERRNO(map_entry.start, AtoiBase(addresses[0], 16)); + ASSIGN_OR_RETURN_ERRNO(map_entry.end, AtoiBase(addresses[1], 16)); + + // Permissions are four bytes of the form rwxp or - if permission not set. + if (parts[1].size() != 4) { + return PosixError(EINVAL, + absl::StrCat("Invalid permission field: ", parts[1])); + } + + map_entry.readable = parts[1][0] == 'r'; + map_entry.writable = parts[1][1] == 'w'; + map_entry.executable = parts[1][2] == 'x'; + map_entry.priv = parts[1][3] == 'p'; + + ASSIGN_OR_RETURN_ERRNO(map_entry.offset, AtoiBase(parts[2], 16)); + + std::vector<std::string> device = absl::StrSplit(parts[3], ':'); + if (device.size() != 2) { + return PosixError(EINVAL, absl::StrCat("Invalid device: ", parts[3])); + } + ASSIGN_OR_RETURN_ERRNO(map_entry.major, AtoiBase(device[0], 16)); + ASSIGN_OR_RETURN_ERRNO(map_entry.minor, AtoiBase(device[1], 16)); + + ASSIGN_OR_RETURN_ERRNO(map_entry.inode, Atoi<int64_t>(parts[4])); + if (parts.size() == 6) { + // A filename is present. + map_entry.filename = parts[5]; + } + + return map_entry; +} + +PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps( + absl::string_view contents) { + std::vector<ProcMapsEntry> entries; + auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); + for (const auto& l : lines) { + LOG(INFO) << "line: " << l; + ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcMapsLine(l)); + entries.push_back(entry); + } + return entries; +} + +PosixErrorOr<bool> IsVsyscallEnabled() { + ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps")); + ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents)); + return std::any_of(maps.begin(), maps.end(), [](const ProcMapsEntry& e) { + return e.filename == "[vsyscall]"; + }); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/proc_util.h b/test/util/proc_util.h new file mode 100644 index 000000000..f8021d92e --- /dev/null +++ b/test/util/proc_util.h @@ -0,0 +1,150 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_PROC_UTIL_H_ +#define GVISOR_TEST_UTIL_PROC_UTIL_H_ + +#include <ostream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "test/util/fs_util.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// ProcMapsEntry contains the data from a single line in /proc/<xxx>/maps. +struct ProcMapsEntry { + uint64_t start; + uint64_t end; + bool readable; + bool writable; + bool executable; + bool priv; + uint64_t offset; + int major; + int minor; + int64_t inode; + std::string filename; +}; + +// Parses a ProcMaps line or returns an error. +PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line); +PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps( + absl::string_view contents); + +// Returns true if vsyscall (emmulation or not) is enabled. +PosixErrorOr<bool> IsVsyscallEnabled(); + +// Printer for ProcMapsEntry. +inline std::ostream& operator<<(std::ostream& os, const ProcMapsEntry& entry) { + std::string str = + absl::StrCat(absl::Hex(entry.start, absl::PadSpec::kZeroPad8), "-", + absl::Hex(entry.end, absl::PadSpec::kZeroPad8), " "); + + absl::StrAppend(&str, entry.readable ? "r" : "-"); + absl::StrAppend(&str, entry.writable ? "w" : "-"); + absl::StrAppend(&str, entry.executable ? "x" : "-"); + absl::StrAppend(&str, entry.priv ? "p" : "s"); + + absl::StrAppend(&str, " ", absl::Hex(entry.offset, absl::PadSpec::kZeroPad8), + " ", absl::Hex(entry.major, absl::PadSpec::kZeroPad2), ":", + absl::Hex(entry.minor, absl::PadSpec::kZeroPad2), " ", + entry.inode); + if (absl::string_view(entry.filename) != "") { + // Pad to column 74 + int pad = 73 - str.length(); + if (pad > 0) { + absl::StrAppend(&str, std::string(pad, ' ')); + } + absl::StrAppend(&str, entry.filename); + } + os << str; + return os; +} + +// Printer for std::vector<ProcMapsEntry>. +inline std::ostream& operator<<(std::ostream& os, + const std::vector<ProcMapsEntry>& vec) { + for (unsigned int i = 0; i < vec.size(); i++) { + os << vec[i]; + if (i != vec.size() - 1) { + os << "\n"; + } + } + return os; +} + +// GMock printer for std::vector<ProcMapsEntry>. +inline void PrintTo(const std::vector<ProcMapsEntry>& vec, std::ostream* os) { + *os << vec; +} + +// Checks that /proc/pid/maps contains all of the passed mappings. +// +// The major, minor, and inode fields are ignored. +MATCHER_P(ContainsMappings, mappings, + "contains mappings:\n" + ::testing::PrintToString(mappings)) { + auto contents_or = GetContents(absl::StrCat("/proc/", arg, "/maps")); + if (!contents_or.ok()) { + *result_listener << "Unable to read mappings: " + << contents_or.error().ToString(); + return false; + } + + auto maps_or = ParseProcMaps(contents_or.ValueOrDie()); + if (!maps_or.ok()) { + *result_listener << "Unable to parse mappings: " + << maps_or.error().ToString(); + return false; + } + + auto maps = std::move(maps_or.ValueOrDie()); + + // Does maps contain all elements in mappings? The comparator ignores + // the major, minor, and inode fields. + bool all_present = true; + std::for_each(mappings.begin(), mappings.end(), [&](const ProcMapsEntry& e1) { + auto it = + std::find_if(maps.begin(), maps.end(), [&e1](const ProcMapsEntry& e2) { + return e1.start == e2.start && e1.end == e2.end && + e1.readable == e2.readable && e1.writable == e2.writable && + e1.executable == e2.executable && e1.priv == e2.priv && + e1.offset == e2.offset && e1.filename == e2.filename; + }); + if (it == maps.end()) { + // It wasn't found. + if (all_present) { + // We will output the message once and then a line for each mapping + // that wasn't found. + all_present = false; + *result_listener << "Got mappings:\n" + << maps << "\nThat were missing:\n"; + } + *result_listener << e1 << "\n"; + } + }); + + return all_present; +} + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_PROC_UTIL_H_ diff --git a/test/util/save_util.cc b/test/util/save_util.cc new file mode 100644 index 000000000..71f4078a7 --- /dev/null +++ b/test/util/save_util.cc @@ -0,0 +1,59 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/save_util.h" + +#include <stddef.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <atomic> +#include <cerrno> + +#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST" + +namespace gvisor { +namespace testing { +namespace { + +bool CooperativeSaveEnabled() { + static bool enabled = getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr; + return enabled; +} + +std::atomic<int> save_disable; + +} // namespace + +DisableSave::DisableSave() { save_disable++; } + +DisableSave::~DisableSave() { reset(); } + +void DisableSave::reset() { + if (!reset_) { + reset_ = true; + save_disable--; + } +} + +void MaybeSave() { + if (CooperativeSaveEnabled() && !save_disable.load()) { + int orig_errno = errno; + syscall(SYS_create_module, nullptr, 0); + errno = orig_errno; + } +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/save_util.h b/test/util/save_util.h new file mode 100644 index 000000000..919e4af3d --- /dev/null +++ b/test/util/save_util.h @@ -0,0 +1,47 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_SAVE_UTIL_H_ +#define GVISOR_TEST_UTIL_SAVE_UTIL_H_ + +namespace gvisor { +namespace testing { +// Disable save prevents saving while the given function executes. +// +// This lasts the duration of the object, unless reset is called. +class DisableSave { + public: + DisableSave(); + ~DisableSave(); + DisableSave(DisableSave const&) = delete; + DisableSave(DisableSave&&) = delete; + DisableSave& operator=(DisableSave const&) = delete; + DisableSave& operator=(DisableSave&&) = delete; + + // reset allows saves to continue, and is called implicitly by the destructor. + // It may be called multiple times safely, but is not thread-safe. + void reset(); + + private: + bool reset_ = false; +}; + +// May perform a co-operative save cycle. +// +// errno is guaranteed to be preserved. +void MaybeSave(); +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_SAVE_UTIL_H_ diff --git a/test/util/signal_util.cc b/test/util/signal_util.cc new file mode 100644 index 000000000..3e2df32a6 --- /dev/null +++ b/test/util/signal_util.cc @@ -0,0 +1,103 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/signal_util.h" + +#include <signal.h> +#include <ostream> + +#include "gtest/gtest.h" +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace { + +struct Range { + int start; + int end; +}; + +// Format a Range as "start-end" or "start" for single value Ranges. +static ::std::ostream& operator<<(::std::ostream& os, const Range& range) { + if (range.end > range.start) { + return os << range.start << '-' << range.end; + } + + return os << range.start; +} + +} // namespace + +// Format a sigset_t as a comma separated list of numeric ranges. +// Empty sigset: [] +// Full sigset: [1-31,34-64] +::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset) { + const char* delim = ""; + Range range = {0, 0}; + + os << '['; + + for (int sig = 1; sig <= gvisor::testing::kMaxSignal; ++sig) { + if (sigismember(&sigset, sig)) { + if (range.start) { + range.end = sig; + } else { + range.start = sig; + range.end = sig; + } + } else if (range.start) { + os << delim << range; + delim = ","; + range.start = 0; + range.end = 0; + } + } + + if (range.start) { + os << delim << range; + } + + return os << ']'; +} + +namespace gvisor { +namespace testing { + +PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa) { + struct sigaction old_sa; + int rc = sigaction(sig, &sa, &old_sa); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "sigaction failed"); + } + return Cleanup([sig, old_sa] { + EXPECT_THAT(sigaction(sig, &old_sa, nullptr), SyscallSucceeds()); + }); +} + +PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set) { + sigset_t old; + int rc = sigprocmask(how, &set, &old); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "sigprocmask failed"); + } + return Cleanup([old] { + EXPECT_THAT(sigprocmask(SIG_SETMASK, &old, nullptr), SyscallSucceeds()); + }); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/signal_util.h b/test/util/signal_util.h new file mode 100644 index 000000000..f58f4c6c4 --- /dev/null +++ b/test/util/signal_util.h @@ -0,0 +1,92 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_SIGNAL_UTIL_H_ +#define GVISOR_TEST_UTIL_SIGNAL_UTIL_H_ + +#include <signal.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <ostream> + +#include "gmock/gmock.h" +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" + +// Format a sigset_t as a comma separated list of numeric ranges. +::std::ostream& operator<<(::std::ostream& os, const sigset_t& sigset); + +namespace gvisor { +namespace testing { + +// The maximum signal number. +static constexpr int kMaxSignal = 64; + +// Wrapper for the tgkill(2) syscall, which glibc does not provide. +inline int tgkill(pid_t tgid, pid_t tid, int sig) { + return syscall(__NR_tgkill, tgid, tid, sig); +} + +// Installs the passed sigaction and returns a cleanup function to restore the +// previous handler when it goes out of scope. +PosixErrorOr<Cleanup> ScopedSigaction(int sig, struct sigaction const& sa); + +// Updates the signal mask as per sigprocmask(2) and returns a cleanup function +// to restore the previous signal mask when it goes out of scope. +PosixErrorOr<Cleanup> ScopedSignalMask(int how, sigset_t const& set); + +// ScopedSignalMask variant that creates a mask of the single signal 'sig'. +inline PosixErrorOr<Cleanup> ScopedSignalMask(int how, int sig) { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, sig); + return ScopedSignalMask(how, set); +} + +// Asserts equality of two sigset_t values. +MATCHER_P(EqualsSigset, value, "equals " + ::testing::PrintToString(value)) { + for (int sig = 1; sig <= kMaxSignal; ++sig) { + if (sigismember(&arg, sig) != sigismember(&value, sig)) { + return false; + } + } + return true; +} + +#ifdef __x86_64__ +// Fault can be used to generate a synchronous SIGSEGV. +// +// This fault can be fixed up in a handler via fixup, below. +inline void Fault() { + // Zero and dereference %ax. + asm("movabs $0, %%rax\r\n" + "mov 0(%%rax), %%rax\r\n" + : + : + : "ax"); +} + +// FixupFault fixes up a fault generated by fault, above. +inline void FixupFault(ucontext* ctx) { + // Skip the bad instruction above. + // + // The encoding is 0x48 0xab 0x00. + ctx->uc_mcontext.gregs[REG_RIP] += 3; +} +#endif + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_SIGNAL_UTIL_H_ diff --git a/test/util/temp_path.cc b/test/util/temp_path.cc new file mode 100644 index 000000000..e45909655 --- /dev/null +++ b/test/util/temp_path.cc @@ -0,0 +1,157 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/temp_path.h" + +#include <unistd.h> +#include <atomic> +#include <cstdlib> + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/util/fs_util.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +std::atomic<uint64_t> global_temp_file_number = ATOMIC_VAR_INIT(1); + +// Return a new temp filename, intended to be unique system-wide. +// +// The global file number helps maintain file naming consistency across +// different runs of a test. +// +// The timestamp is necessary because the test infrastructure invokes each +// test case in a separate process (resetting global_temp_file_number) and +// potentially in parallel, which allows for races between selecting and using a +// name. +std::string NextTempBasename() { + return absl::StrCat("gvisor_test_temp_", global_temp_file_number++, "_", + absl::ToUnixNanos(absl::Now())); +} + +void TryDeleteRecursively(std::string const& path) { + if (!path.empty()) { + int undeleted_dirs = 0; + int undeleted_files = 0; + auto status = RecursivelyDelete(path, &undeleted_dirs, &undeleted_files); + if (undeleted_dirs || undeleted_files || !status.ok()) { + LOG(WARNING) << path << ": failed to delete " << undeleted_dirs + << " directories and " << undeleted_files + << " files: " << status; + } + } +} + +} // namespace + +constexpr mode_t TempPath::kDefaultFileMode; +constexpr mode_t TempPath::kDefaultDirMode; + +std::string NewTempAbsPathInDir(absl::string_view const dir) { + return JoinPath(dir, NextTempBasename()); +} + +std::string NewTempAbsPath() { return NewTempAbsPathInDir(GetAbsoluteTestTmpdir()); } + +std::string NewTempRelPath() { return NextTempBasename(); } + +std::string GetAbsoluteTestTmpdir() { + char* env_tmpdir = getenv("TEST_TMPDIR"); + std::string tmp_dir = env_tmpdir != nullptr ? std::string(env_tmpdir) : "/tmp"; + return MakeAbsolute(tmp_dir, "").ValueOrDie(); +} + +PosixErrorOr<TempPath> TempPath::CreateFileWith(absl::string_view const parent, + absl::string_view const content, + mode_t const mode) { + return CreateIn(parent, [=](absl::string_view path) -> PosixError { + // SetContents will call open(O_WRONLY) with the given mode. If the + // mode is not user-writable, save/restore cannot preserve the fd. Hence + // the little permission dance that's done here. + auto res = CreateWithContents(path, content, mode | 0200); + RETURN_IF_ERRNO(res); + + return Chmod(path, mode); + }); +} + +PosixErrorOr<TempPath> TempPath::CreateDirWith(absl::string_view const parent, + mode_t const mode) { + return CreateIn(parent, + [=](absl::string_view path) { return Mkdir(path, mode); }); +} + +PosixErrorOr<TempPath> TempPath::CreateSymlinkTo(absl::string_view const parent, + std::string const& dest) { + return CreateIn(parent, [=](absl::string_view path) { + int ret = symlink(dest.c_str(), std::string(path).c_str()); + if (ret != 0) { + return PosixError(errno, "symlink failed"); + } + return NoError(); + }); +} + +PosixErrorOr<TempPath> TempPath::CreateFileIn(absl::string_view const parent) { + return TempPath::CreateFileWith(parent, absl::string_view(), + kDefaultFileMode); +} + +PosixErrorOr<TempPath> TempPath::CreateDirIn(absl::string_view const parent) { + return TempPath::CreateDirWith(parent, kDefaultDirMode); +} + +PosixErrorOr<TempPath> TempPath::CreateFileMode(mode_t mode) { + return TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), absl::string_view(), + mode); +} + +PosixErrorOr<TempPath> TempPath::CreateFile() { + return TempPath::CreateFileIn(GetAbsoluteTestTmpdir()); +} + +PosixErrorOr<TempPath> TempPath::CreateDir() { + return TempPath::CreateDirIn(GetAbsoluteTestTmpdir()); +} + +TempPath::~TempPath() { TryDeleteRecursively(path_); } + +TempPath::TempPath(TempPath&& orig) { reset(orig.release()); } + +TempPath& TempPath::operator=(TempPath&& orig) { + reset(orig.release()); + return *this; +} + +std::string TempPath::reset(std::string newpath) { + std::string path = path_; + TryDeleteRecursively(path_); + path_ = std::move(newpath); + return path; +} + +std::string TempPath::release() { + std::string path = path_; + path_ = std::string(); + return path; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/temp_path.h b/test/util/temp_path.h new file mode 100644 index 000000000..33eb6a72c --- /dev/null +++ b/test/util/temp_path.h @@ -0,0 +1,134 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_TEMP_PATH_H_ +#define GVISOR_TEST_UTIL_TEMP_PATH_H_ + +#include <sys/stat.h> +#include <string> +#include <utility> + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// Returns an absolute path for a file in `dir` that does not yet exist. +// Distinct calls to NewTempAbsPathInDir from the same process, even from +// multiple threads, are guaranteed to return different paths. Distinct calls to +// NewTempAbsPathInDir from different processes are not synchronized. +std::string NewTempAbsPathInDir(absl::string_view base); + +// Like NewTempAbsPathInDir, but the returned path is in the test's temporary +// directory, as provided by the testing framework. +std::string NewTempAbsPath(); + +// Like NewTempAbsPathInDir, but the returned path is relative (to the current +// working directory). +std::string NewTempRelPath(); + +// Returns the absolute path for the test temp dir. +std::string GetAbsoluteTestTmpdir(); + +// Represents a temporary file or directory. +class TempPath { + public: + // Default creation mode for files. + static constexpr mode_t kDefaultFileMode = 0644; + + // Default creation mode for directories. + static constexpr mode_t kDefaultDirMode = 0755; + + // Creates a temporary file in directory `parent` with mode `mode` and + // contents `content`. + static PosixErrorOr<TempPath> CreateFileWith(absl::string_view parent, + absl::string_view content, + mode_t mode); + + // Creates an empty temporary subdirectory in directory `parent` with mode + // `mode`. + static PosixErrorOr<TempPath> CreateDirWith(absl::string_view parent, + mode_t mode); + + // Creates a temporary symlink in directory `parent` to destination `dest`. + static PosixErrorOr<TempPath> CreateSymlinkTo(absl::string_view parent, + std::string const& dest); + + // Creates an empty temporary file in directory `parent` with mode + // kDefaultFileMode. + static PosixErrorOr<TempPath> CreateFileIn(absl::string_view parent); + + // Creates an empty temporary subdirectory in directory `parent` with mode + // kDefaultDirMode. + static PosixErrorOr<TempPath> CreateDirIn(absl::string_view parent); + + // Creates an empty temporary file in the test's temporary directory with mode + // `mode`. + static PosixErrorOr<TempPath> CreateFileMode(mode_t mode); + + // Creates an empty temporary file in the test's temporary directory with + // mode kDefaultFileMode. + static PosixErrorOr<TempPath> CreateFile(); + + // Creates an empty temporary subdirectory in the test's temporary directory + // with mode kDefaultDirMode. + static PosixErrorOr<TempPath> CreateDir(); + + // Constructs a TempPath that represents nothing. + TempPath() = default; + + // Constructs a TempPath that represents the given path, which will be deleted + // when the TempPath is destroyed. + explicit TempPath(std::string path) : path_(std::move(path)) {} + + // Attempts to delete the represented temporary file or directory (in the + // latter case, also attempts to delete its contents). + ~TempPath(); + + // Attempts to delete the represented temporary file or directory, then + // transfers ownership of the path represented by orig to this TempPath. + TempPath(TempPath&& orig); + TempPath& operator=(TempPath&& orig); + + // Changes the path this TempPath represents. If the TempPath already + // represented a path, deletes and returns that path. Otherwise returns the + // empty std::string. + std::string reset(std::string newpath); + std::string reset() { return reset(""); } + + // Forgets and returns the path this TempPath represents. The path is not + // deleted. + std::string release(); + + // Returns the path this TempPath represents. + std::string path() const { return path_; } + + private: + template <typename F> + static PosixErrorOr<TempPath> CreateIn(absl::string_view const parent, + F const& f) { + std::string path = NewTempAbsPathInDir(parent); + RETURN_IF_ERRNO(f(path)); + return TempPath(std::move(path)); + } + + std::string path_; +}; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_TEMP_PATH_H_ diff --git a/test/util/test_main.cc b/test/util/test_main.cc new file mode 100644 index 000000000..4c6b5e860 --- /dev/null +++ b/test/util/test_main.cc @@ -0,0 +1,20 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/test_util.h" + +int main(int argc, char** argv) { + gvisor::testing::TestInit(&argc, &argv); + return RUN_ALL_TESTS(); +} diff --git a/test/util/test_util.cc b/test/util/test_util.cc new file mode 100644 index 000000000..7b40260d1 --- /dev/null +++ b/test/util/test_util.cc @@ -0,0 +1,248 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/test_util.h" + +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include <ctime> +#include <vector> +#include "absl/base/attributes.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/time/time.h" +#include "test/util/fs_util.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +#define TEST_ON_GVISOR "TEST_ON_GVISOR" + +bool IsRunningOnGvisor() { return GvisorPlatform() != Platform::kNative; } + +Platform GvisorPlatform() { + // Set by runner.go. + char* env = getenv(TEST_ON_GVISOR); + if (!env) { + return Platform::kNative; + } + if (strcmp(env, "ptrace") == 0) { + return Platform::kPtrace; + } + if (strcmp(env, "kvm") == 0) { + return Platform::kKVM; + } + LOG(FATAL) << "unknown platform " << env; + __builtin_unreachable(); +} + +// Inline cpuid instruction. Preserve %ebx/%rbx register. In PIC compilations +// %ebx contains the address of the global offset table. %rbx is occasionally +// used to address stack variables in presence of dynamic allocas. +#if defined(__x86_64__) +#define GETCPUID(a, b, c, d, a_inp, c_inp) \ + asm("mov %%rbx, %%rdi\n" \ + "cpuid\n" \ + "xchg %%rdi, %%rbx\n" \ + : "=a"(a), "=D"(b), "=c"(c), "=d"(d) \ + : "a"(a_inp), "2"(c_inp)) +#endif // defined(__x86_64__) + +CPUVendor GetCPUVendor() { + uint32_t eax, ebx, ecx, edx; + std::string vendor_str; + // Get vendor std::string (issue CPUID with eax = 0) + GETCPUID(eax, ebx, ecx, edx, 0, 0); + vendor_str.append(reinterpret_cast<char*>(&ebx), 4); + vendor_str.append(reinterpret_cast<char*>(&edx), 4); + vendor_str.append(reinterpret_cast<char*>(&ecx), 4); + if (vendor_str == "GenuineIntel") { + return CPUVendor::kIntel; + } else if (vendor_str == "AuthenticAMD") { + return CPUVendor::kAMD; + } + return CPUVendor::kUnknownVendor; +} + +bool operator==(const KernelVersion& first, const KernelVersion& second) { + return first.major == second.major && first.minor == second.minor && + first.micro == second.micro; +} + +PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_str) { + KernelVersion version = {}; + std::vector<std::string> values = absl::StrSplit(vers_str, absl::ByAnyChar(".-")); + if (values.size() == 2) { + ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0])); + ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1])); + return version; + } else if (values.size() >= 3) { + ASSIGN_OR_RETURN_ERRNO(version.major, Atoi<int>(values[0])); + ASSIGN_OR_RETURN_ERRNO(version.minor, Atoi<int>(values[1])); + ASSIGN_OR_RETURN_ERRNO(version.micro, Atoi<int>(values[2])); + return version; + } + return PosixError(EINVAL, absl::StrCat("Unknown kernel release: ", vers_str)); +} + +PosixErrorOr<KernelVersion> GetKernelVersion() { + utsname buf; + RETURN_ERROR_IF_SYSCALL_FAIL(uname(&buf)); + return ParseKernelVersion(buf.release); +} + +void SetupGvisorDeathTest() { +} + +std::string CPUSetToString(const cpu_set_t& set, size_t cpus) { + std::string str = "cpuset["; + for (unsigned int n = 0; n < cpus; n++) { + if (CPU_ISSET(n, &set)) { + if (n != 0) { + absl::StrAppend(&str, " "); + } + absl::StrAppend(&str, n); + } + } + absl::StrAppend(&str, "]"); + return str; +} + +// An overloaded operator<< makes it easy to dump the value of an OpenFd. +std::ostream& operator<<(std::ostream& out, OpenFd const& ofd) { + out << ofd.fd << " -> " << ofd.link; + return out; +} + +// An overloaded operator<< makes it easy to dump a vector of OpenFDs. +std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v) { + for (const auto& ofd : v) { + out << ofd << std::endl; + } + return out; +} + +PosixErrorOr<std::vector<OpenFd>> GetOpenFDs() { + // Get the results from /proc/self/fd. + ASSIGN_OR_RETURN_ERRNO(auto dir_list, + ListDir("/proc/self/fd", /*skipdots=*/true)); + + std::vector<OpenFd> ret_fds; + for (const auto& str_fd : dir_list) { + OpenFd open_fd = {}; + ASSIGN_OR_RETURN_ERRNO(open_fd.fd, Atoi<int>(str_fd)); + std::string path = absl::StrCat("/proc/self/fd/", open_fd.fd); + + // Resolve the link. + char buf[PATH_MAX] = {}; + int ret = readlink(path.c_str(), buf, sizeof(buf)); + if (ret < 0) { + if (errno == ENOENT) { + // The FD may have been closed, let's be resilient. + continue; + } + + return PosixError( + errno, absl::StrCat("readlink of ", path, " returned errno ", errno)); + } + open_fd.link = std::string(buf, ret); + ret_fds.emplace_back(std::move(open_fd)); + } + return ret_fds; +} + +PosixErrorOr<uint64_t> Links(const std::string& path) { + struct stat st; + if (stat(path.c_str(), &st)) { + return PosixError(errno, absl::StrCat("Failed to stat ", path)); + } + return static_cast<uint64_t>(st.st_nlink); +} + +void RandomizeBuffer(void* buffer, size_t len) { + struct timespec ts = {}; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint32_t seed = static_cast<uint32_t>(ts.tv_nsec); + char* const buf = static_cast<char*>(buffer); + for (size_t i = 0; i < len; i++) { + buf[i] = rand_r(&seed) % 255; + } +} + +std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size, + void* buf, + size_t buflen) { + std::vector<std::vector<struct iovec>> result; + for (uint64_t offset = 0; offset < total_size;) { + auto& iovec_array = *result.emplace(result.end()); + + for (; offset < total_size && iovec_array.size() < IOV_MAX; + offset += buflen) { + struct iovec iov = {}; + iov.iov_base = buf; + iov.iov_len = std::min<uint64_t>(total_size - offset, buflen); + iovec_array.push_back(iov); + } + } + + return result; +} + +void SleepSafe(absl::Duration duration) { + if (duration == absl::ZeroDuration()) { + return; + } + + struct timespec ts = absl::ToTimespec(duration); + int ret; + while (1) { + ret = syscall(__NR_nanosleep, &ts, &ts); + if (ret == 0 || (ret <= 0 && errno != EINTR)) { + break; + } + } +} + +uint64_t Megabytes(uint64_t n) { + // Overflow check, upper 20 bits in n shouldn't be set. + CHECK(!(0xfffff00000000000 & n)); + return n << 20; +} + +bool Equivalent(uint64_t current, uint64_t target, double tolerance) { + auto abs_diff = target > current ? target - current : current - target; + return abs_diff <= static_cast<uint64_t>(tolerance * target); +} +void TestInit(int* argc, char*** argv) { + ::testing::InitGoogleTest(argc, *argv); + ::gflags::ParseCommandLineFlags(argc, argv, true); + + // Always mask SIGPIPE as it's common and tests aren't expected to handle it. + struct sigaction sa = {}; + sa.sa_handler = SIG_IGN; + TEST_CHECK(sigaction(SIGPIPE, &sa, nullptr) == 0); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/test_util.h b/test/util/test_util.h new file mode 100644 index 000000000..2a7609e5c --- /dev/null +++ b/test/util/test_util.h @@ -0,0 +1,794 @@ +// Copyright 2018 Google LLC +// +// 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. + +// Utilities for syscall testing. +// +// Initialization +// ============== +// +// Prior to calling RUN_ALL_TESTS, all tests must use TestInit(&argc, &argv). +// See the TestInit function for exact side-effects and semantics. +// +// Configuration +// ============= +// +// IsRunningOnGvisor returns true if the test is known to be running on gVisor. +// GvisorPlatform can be used to get more detail: +// +// switch (GvisorPlatform()) { +// case Platform::kNative: +// case Platform::kGvisor: +// EXPECT_THAT(mmap(...), SyscallSucceeds()); +// break; +// case Platform::kPtrace: +// EXPECT_THAT(mmap(...), SyscallFailsWithErrno(ENOSYS)); +// break; +// } +// +// Matchers +// ======== +// +// ElementOf(xs) matches if the matched value is equal to an element of the +// container xs. Example: +// +// // PASS +// EXPECT_THAT(1, ElementOf({0, 1, 2})); +// +// // FAIL +// // Value of: 3 +// // Expected: one of {0, 1, 2} +// // Actual: 3 +// EXPECT_THAT(3, ElementOf({0, 1, 2})); +// +// SyscallSucceeds() matches if the syscall is successful. A successful syscall +// is defined by either a return value not equal to -1, or a return value of -1 +// with an errno of 0 (which is a possible successful return for e.g. +// PTRACE_PEEK). Example: +// +// // PASS +// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallSucceeds()); +// +// // FAIL +// // Value of: open("/", O_RDWR) +// // Expected: not -1 (success) +// // Actual: -1 (of type int), with errno 21 (Is a directory) +// EXPECT_THAT(open("/", O_RDWR), SyscallSucceeds()); +// +// SyscallSucceedsWithValue(m) matches if the syscall is successful, and the +// value also matches m. Example: +// +// // PASS +// EXPECT_THAT(read(4, buf, 8192), SyscallSucceedsWithValue(8192)); +// +// // FAIL +// // Value of: read(-1, buf, 8192) +// // Expected: is equal to 8192 +// // Actual: -1 (of type long), with errno 9 (Bad file number) +// EXPECT_THAT(read(-1, buf, 8192), SyscallSucceedsWithValue(8192)); +// +// // FAIL +// // Value of: read(4, buf, 1) +// // Expected: is > 4096 +// // Actual: 1 (of type long) +// EXPECT_THAT(read(4, buf, 1), SyscallSucceedsWithValue(Gt(4096))); +// +// SyscallFails() matches if the syscall is unsuccessful. An unsuccessful +// syscall is defined by a return value of -1 with a non-zero errno. Example: +// +// // PASS +// EXPECT_THAT(open("/", O_RDWR), SyscallFails()); +// +// // FAIL +// // Value of: open("/dev/null", O_RDONLY) +// // Expected: -1 (failure) +// // Actual: 0 (of type int) +// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFails()); +// +// SyscallFailsWithErrno(m) matches if the syscall is unsuccessful, and errno +// matches m. Example: +// +// // PASS +// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EISDIR)); +// +// // PASS +// EXPECT_THAT(open("/etc/passwd", O_RDWR | O_DIRECTORY), +// SyscallFailsWithErrno(AnyOf(EACCES, ENOTDIR))); +// +// // FAIL +// // Value of: open("/dev/null", O_RDONLY) +// // Expected: -1 (failure) with errno 21 (Is a directory) +// // Actual: 0 (of type int) +// EXPECT_THAT(open("/dev/null", O_RDONLY), SyscallFailsWithErrno(EISDIR)); +// +// // FAIL +// // Value of: open("/", O_RDWR) +// // Expected: -1 (failure) with errno 22 (Invalid argument) +// // Actual: -1 (of type int), failure, but with errno 21 (Is a directory) +// EXPECT_THAT(open("/", O_RDWR), SyscallFailsWithErrno(EINVAL)); +// +// Because the syscall matchers encode save/restore functionality, their meaning +// should not be inverted via Not. That is, AnyOf(SyscallSucceedsWithValue(1), +// SyscallSucceedsWithValue(2)) is permitted, but not +// Not(SyscallFailsWithErrno(EPERM)). +// +// Syscalls +// ======== +// +// RetryEINTR wraps a function that returns -1 and sets errno on failure +// to be automatically retried when EINTR occurs. Example: +// +// auto rv = RetryEINTR(waitpid)(pid, &status, 0); +// +// ReadFd/WriteFd/PreadFd/PwriteFd are interface-compatible wrappers around the +// read/write/pread/pwrite syscalls to handle both EINTR and partial +// reads/writes. Example: +// +// EXPECT_THAT(ReadFd(fd, &buf, size), SyscallSucceedsWithValue(size)); +// +// General Utilities +// ================= +// +// ApplyVec(f, xs) returns a vector containing the result of applying function +// `f` to each value in `xs`. +// +// AllBitwiseCombinations takes a variadic number of ranges containing integers +// and returns a vector containing every integer that can be formed by ORing +// together exactly one integer from each list. List<T> is an alias for +// std::initializer_list<T> that makes AllBitwiseCombinations more ergonomic to +// use with list literals (initializer lists do not otherwise participate in +// template argument deduction). Example: +// +// EXPECT_THAT( +// AllBitwiseCombinations<int>( +// List<int>{SOCK_DGRAM, SOCK_STREAM}, +// List<int>{0, SOCK_NONBLOCK}), +// Contains({SOCK_DGRAM, SOCK_STREAM, SOCK_DGRAM | SOCK_NONBLOCK, +// SOCK_STREAM | SOCK_NONBLOCK})); +// +// VecCat takes a variadic number of containers and returns a vector containing +// the concatenated contents. +// +// VecAppend takes an initial container and a variadic number of containers and +// appends each to the initial container. +// +// RandomizeBuffer will use MTRandom to fill the given buffer with random bytes. +// +// GenerateIovecs will return the smallest number of iovec arrays for writing a +// given total number of bytes to a file, each iovec array size up to IOV_MAX, +// each iovec in each array pointing to the same buffer. + +#ifndef GVISOR_TEST_UTIL_TEST_UTIL_H_ +#define GVISOR_TEST_UTIL_TEST_UTIL_H_ + +#include <stddef.h> +#include <stdlib.h> +#include <sys/uio.h> +#include <unistd.h> + +#include <algorithm> +#include <cerrno> +#include <initializer_list> +#include <iterator> +#include <string> +#include <thread> // NOLINT: using std::thread::hardware_concurrency(). +#include <utility> +#include <vector> +#include <gflags/gflags.h> +#include <glog/logging.h> +#include "gmock/gmock.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "test/util/fs_util.h" +#include "test/util/logging.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" + +namespace gvisor { +namespace testing { + +// TestInit must be called prior to RUN_ALL_TESTS. +// +// This parses all arguments and adjusts argc and argv appropriately. +// +// TestInit may create background threads. +void TestInit(int* argc, char*** argv); + +// SKIP_IF may be used to skip a test case. +// +// These cases are still emitted, but a SKIPPED line will appear. +#define SKIP_IF(expr) \ + do { \ + if (expr) { \ + std::cout << "\033[0;33m[ SKIPPED ]\033[m => " << #expr << std::endl; \ + return; \ + } \ + } while (0) + +#define SKIP_BEFORE_KERNEL(maj, min) \ + do { \ + auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); \ + SKIP_IF(version.major < (maj) || \ + (version.major == (maj) && version.minor < (min))); \ + } while (0) + +enum class Platform { + kNative, + kKVM, + kPtrace, +}; +bool IsRunningOnGvisor(); +Platform GvisorPlatform(); + +void SetupGvisorDeathTest(); + +struct KernelVersion { + int major; + int minor; + int micro; +}; + +bool operator==(const KernelVersion& first, const KernelVersion& second); + +PosixErrorOr<KernelVersion> ParseKernelVersion(absl::string_view vers_string); +PosixErrorOr<KernelVersion> GetKernelVersion(); + +static const size_t kPageSize = sysconf(_SC_PAGESIZE); + +enum class CPUVendor { kIntel, kAMD, kUnknownVendor }; + +CPUVendor GetCPUVendor(); + +inline int NumCPUs() { return std::thread::hardware_concurrency(); } + +// Converts cpu_set_t to a std::string for easy examination. +std::string CPUSetToString(const cpu_set_t& set, size_t cpus = CPU_SETSIZE); + +struct OpenFd { + // fd is the open file descriptor number. + int fd = -1; + + // link is the resolution of the symbolic link. + std::string link; +}; + +// Make it easier to log OpenFds to error streams. +std::ostream& operator<<(std::ostream& out, std::vector<OpenFd> const& v); +std::ostream& operator<<(std::ostream& out, OpenFd const& ofd); + +// Gets a detailed list of open fds for this process. +PosixErrorOr<std::vector<OpenFd>> GetOpenFDs(); + +// Returns the number of hard links to a path. +PosixErrorOr<uint64_t> Links(const std::string& path); + +namespace internal { + +inline std::string ErrnoWithMessage(int const errnum) { + char buf[1024] = {}; + const char* str = strerror_r(errnum, buf, sizeof(buf)); + if (str == nullptr || str[0] == '\0') { + snprintf(buf, sizeof(buf), "Unknown error %d", errnum); + str = buf; + } + return absl::StrCat(errnum, " (", str, ")"); +} + +template <typename Container> +class ElementOfMatcher { + public: + explicit ElementOfMatcher(Container container) + : container_(::std::move(container)) {} + + template <typename T> + bool MatchAndExplain(T const& rv, + ::testing::MatchResultListener* const listener) const { + using std::count; + return count(container_.begin(), container_.end(), rv) != 0; + } + + void DescribeTo(::std::ostream* const os) const { + *os << "one of {"; + char const* sep = ""; + for (auto const& elem : container_) { + *os << sep << elem; + sep = ", "; + } + *os << "}"; + } + + void DescribeNegationTo(::std::ostream* const os) const { + *os << "none of {"; + char const* sep = ""; + for (auto const& elem : container_) { + *os << sep << elem; + sep = ", "; + } + *os << "}"; + } + + private: + Container const container_; +}; + +template <typename E> +class SyscallSuccessMatcher { + public: + explicit SyscallSuccessMatcher(E expected) + : expected_(::std::move(expected)) {} + + template <typename T> + operator ::testing::Matcher<T>() const { + // E is one of three things: + // - T, or a type losslessly and implicitly convertible to T. + // - A monomorphic Matcher<T>. + // - A polymorphic matcher. + // SafeMatcherCast handles any of the above correctly. + // + // Similarly, gMock will invoke this conversion operator to obtain a + // monomorphic matcher (this is how polymorphic matchers are implemented). + return ::testing::MakeMatcher( + new Impl<T>(::testing::SafeMatcherCast<T>(expected_))); + } + + private: + template <typename T> + class Impl : public ::testing::MatcherInterface<T> { + public: + explicit Impl(::testing::Matcher<T> matcher) + : matcher_(::std::move(matcher)) {} + + bool MatchAndExplain( + T const& rv, + ::testing::MatchResultListener* const listener) const override { + if (rv == static_cast<decltype(rv)>(-1) && errno != 0) { + *listener << "with errno " << ErrnoWithMessage(errno); + return false; + } + bool match = matcher_.MatchAndExplain(rv, listener); + if (match) { + MaybeSave(); + } + return match; + } + + void DescribeTo(::std::ostream* const os) const override { + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* const os) const override { + matcher_.DescribeNegationTo(os); + } + + private: + ::testing::Matcher<T> matcher_; + }; + + private: + E expected_; +}; + +// A polymorphic matcher equivalent to ::testing::internal::AnyMatcher, except +// not in namespace ::testing::internal, and describing SyscallSucceeds()'s +// match constraints (which are enforced by SyscallSuccessMatcher::Impl). +class AnySuccessValueMatcher { + public: + template <typename T> + operator ::testing::Matcher<T>() const { + return ::testing::MakeMatcher(new Impl<T>()); + } + + private: + template <typename T> + class Impl : public ::testing::MatcherInterface<T> { + public: + bool MatchAndExplain( + T const& rv, + ::testing::MatchResultListener* const listener) const override { + return true; + } + + void DescribeTo(::std::ostream* const os) const override { + *os << "not -1 (success)"; + } + + void DescribeNegationTo(::std::ostream* const os) const override { + *os << "-1 (failure)"; + } + }; +}; + +class SyscallFailureMatcher { + public: + explicit SyscallFailureMatcher(::testing::Matcher<int> errno_matcher) + : errno_matcher_(std::move(errno_matcher)) {} + + template <typename T> + bool MatchAndExplain(T const& rv, + ::testing::MatchResultListener* const listener) const { + if (rv != static_cast<decltype(rv)>(-1)) { + return false; + } + int actual_errno = errno; + *listener << "with errno " << ErrnoWithMessage(actual_errno); + bool match = errno_matcher_.MatchAndExplain(actual_errno, listener); + if (match) { + MaybeSave(); + } + return match; + } + + void DescribeTo(::std::ostream* const os) const { + *os << "-1 (failure), with errno "; + errno_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* const os) const { + *os << "not -1 (success), with errno "; + errno_matcher_.DescribeNegationTo(os); + } + + private: + ::testing::Matcher<int> errno_matcher_; +}; + +class SpecificErrnoMatcher : public ::testing::MatcherInterface<int> { + public: + explicit SpecificErrnoMatcher(int const expected) : expected_(expected) {} + + bool MatchAndExplain( + int const actual_errno, + ::testing::MatchResultListener* const listener) const override { + return actual_errno == expected_; + } + + void DescribeTo(::std::ostream* const os) const override { + *os << ErrnoWithMessage(expected_); + } + + void DescribeNegationTo(::std::ostream* const os) const override { + *os << "not " << ErrnoWithMessage(expected_); + } + + private: + int const expected_; +}; + +inline ::testing::Matcher<int> SpecificErrno(int const expected) { + return ::testing::MakeMatcher(new SpecificErrnoMatcher(expected)); +} + +} // namespace internal + +template <typename Container> +inline ::testing::PolymorphicMatcher<internal::ElementOfMatcher<Container>> +ElementOf(Container container) { + return ::testing::MakePolymorphicMatcher( + internal::ElementOfMatcher<Container>(::std::move(container))); +} + +template <typename T> +inline ::testing::PolymorphicMatcher< + internal::ElementOfMatcher<::std::vector<T>>> +ElementOf(::std::initializer_list<T> elems) { + return ::testing::MakePolymorphicMatcher( + internal::ElementOfMatcher<::std::vector<T>>(::std::vector<T>(elems))); +} + +template <typename E> +inline internal::SyscallSuccessMatcher<E> SyscallSucceedsWithValue(E expected) { + return internal::SyscallSuccessMatcher<E>(::std::move(expected)); +} + +inline internal::SyscallSuccessMatcher<internal::AnySuccessValueMatcher> +SyscallSucceeds() { + return SyscallSucceedsWithValue( + ::gvisor::testing::internal::AnySuccessValueMatcher()); +} + +inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher> +SyscallFailsWithErrno(::testing::Matcher<int> expected) { + return ::testing::MakePolymorphicMatcher( + internal::SyscallFailureMatcher(::std::move(expected))); +} + +// Overload taking an int so that SyscallFailsWithErrno(<specific errno>) uses +// internal::SpecificErrno (which stringifies the errno) rather than +// ::testing::Eq (which doesn't). +inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher> +SyscallFailsWithErrno(int const expected) { + return SyscallFailsWithErrno(internal::SpecificErrno(expected)); +} + +inline ::testing::PolymorphicMatcher<internal::SyscallFailureMatcher> +SyscallFails() { + return SyscallFailsWithErrno(::testing::Gt(0)); +} + +// As of GCC 7.2, -Wall => -Wc++17-compat => -Wnoexcept-type generates an +// irrelevant, non-actionable warning about ABI compatibility when +// RetryEINTRImpl is constructed with a noexcept function, such as glibc's +// syscall(). See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80985. +#if defined(__GNUC__) && !defined(__clang__) && \ + (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnoexcept-type" +#endif + +namespace internal { + +template <typename F> +struct RetryEINTRImpl { + F const f; + + explicit constexpr RetryEINTRImpl(F f) : f(std::move(f)) {} + + template <typename... Args> + auto operator()(Args&&... args) const + -> decltype(f(std::forward<Args>(args)...)) { + while (true) { + errno = 0; + auto const ret = f(std::forward<Args>(args)...); + if (ret != -1 || errno != EINTR) { + return ret; + } + } + } +}; + +} // namespace internal + +template <typename F> +constexpr internal::RetryEINTRImpl<F> RetryEINTR(F&& f) { + return internal::RetryEINTRImpl<F>(std::forward<F>(f)); +} + +#if defined(__GNUC__) && !defined(__clang__) && \ + (__GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 2)) +#pragma GCC diagnostic pop +#endif + +namespace internal { + +template <typename F> +ssize_t ApplyFileIoSyscall(F const& f, size_t const count) { + size_t completed = 0; + // `do ... while` because some callers actually want to make a syscall with a + // count of 0. + do { + auto const cur = RetryEINTR(f)(completed); + if (cur < 0) { + return cur; + } else if (cur == 0) { + break; + } + completed += cur; + } while (completed < count); + return completed; +} + +} // namespace internal + +inline ssize_t ReadFd(int fd, void* buf, size_t count) { + return internal::ApplyFileIoSyscall( + [&](size_t completed) { + return read(fd, static_cast<char*>(buf) + completed, count - completed); + }, + count); +} + +inline ssize_t WriteFd(int fd, void const* buf, size_t count) { + return internal::ApplyFileIoSyscall( + [&](size_t completed) { + return write(fd, static_cast<char const*>(buf) + completed, + count - completed); + }, + count); +} + +inline ssize_t PreadFd(int fd, void* buf, size_t count, off_t offset) { + return internal::ApplyFileIoSyscall( + [&](size_t completed) { + return pread(fd, static_cast<char*>(buf) + completed, count - completed, + offset + completed); + }, + count); +} + +inline ssize_t PwriteFd(int fd, void const* buf, size_t count, off_t offset) { + return internal::ApplyFileIoSyscall( + [&](size_t completed) { + return pwrite(fd, static_cast<char const*>(buf) + completed, + count - completed, offset + completed); + }, + count); +} + +template <typename T> +using List = std::initializer_list<T>; + +namespace internal { + +template <typename T> +void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current) { + combinations->push_back(current); +} + +template <typename T, typename Arg, typename... Args> +void AppendAllBitwiseCombinations(std::vector<T>* combinations, T current, + Arg&& next, Args&&... rest) { + for (auto const option : next) { + AppendAllBitwiseCombinations(combinations, current | option, rest...); + } +} + +inline size_t CombinedSize(size_t accum) { return accum; } + +template <typename T, typename... Args> +size_t CombinedSize(size_t accum, T const& x, Args&&... xs) { + return CombinedSize(accum + x.size(), std::forward<Args>(xs)...); +} + +// Base case: no more containers, so do nothing. +template <typename T> +void DoMoveExtendContainer(T* c) {} + +// Append each container next to c. +template <typename T, typename U, typename... Args> +void DoMoveExtendContainer(T* c, U&& next, Args&&... rest) { + std::move(std::begin(next), std::end(next), std::back_inserter(*c)); + DoMoveExtendContainer(c, std::forward<Args>(rest)...); +} + +} // namespace internal + +template <typename T = int> +std::vector<T> AllBitwiseCombinations() { + return std::vector<T>(); +} + +template <typename T = int, typename... Args> +std::vector<T> AllBitwiseCombinations(Args&&... args) { + std::vector<T> combinations; + internal::AppendAllBitwiseCombinations(&combinations, 0, args...); + return combinations; +} + +template <typename T, typename U, typename F> +std::vector<T> ApplyVec(F const& f, std::vector<U> const& us) { + std::vector<T> vec; + vec.reserve(us.size()); + for (auto const& u : us) { + vec.push_back(f(u)); + } + return vec; +} + +template <typename T, typename U> +std::vector<T> ApplyVecToVec(std::vector<std::function<T(U)>> const& fs, + std::vector<U> const& us) { + std::vector<T> vec; + vec.reserve(us.size() * fs.size()); + for (auto const& f : fs) { + for (auto const& u : us) { + vec.push_back(f(u)); + } + } + return vec; +} + +// Moves all elements from the containers `args` to the end of `c`. +template <typename T, typename... Args> +void VecAppend(T* c, Args&&... args) { + c->reserve(internal::CombinedSize(c->size(), args...)); + internal::DoMoveExtendContainer(c, std::forward<Args>(args)...); +} + +// Returns a vector containing the concatenated contents of the containers +// `args`. +template <typename T, typename... Args> +std::vector<T> VecCat(Args&&... args) { + std::vector<T> combined; + VecAppend(&combined, std::forward<Args>(args)...); + return combined; +} + +#define RETURN_ERROR_IF_SYSCALL_FAIL(syscall) \ + do { \ + if ((syscall) < 0 && errno != 0) { \ + return PosixError(errno, #syscall); \ + } \ + } while (false) + +// Fill the given buffer with random bytes. +void RandomizeBuffer(void* buffer, size_t len); + +template <typename T> +inline PosixErrorOr<T> Atoi(absl::string_view str) { + T ret; + if (!absl::SimpleAtoi<T>(str, &ret)) { + return PosixError(EINVAL, "String not a number."); + } + return ret; +} + +inline PosixErrorOr<uint64_t> AtoiBase(absl::string_view str, int base) { + if (base > 255 || base < 2) { + return PosixError(EINVAL, "Invalid Base"); + } + + uint64_t ret = 0; + if (!absl::numbers_internal::safe_strtou64_base(str, &ret, base)) { + return PosixError(EINVAL, "String not a number."); + } + + return ret; +} + +inline PosixErrorOr<double> Atod(absl::string_view str) { + double ret; + if (!absl::SimpleAtod(str, &ret)) { + return PosixError(EINVAL, "String not a double type."); + } + return ret; +} + +inline PosixErrorOr<float> Atof(absl::string_view str) { + float ret; + if (!absl::SimpleAtof(str, &ret)) { + return PosixError(EINVAL, "String not a float type."); + } + return ret; +} + +// Return the smallest number of iovec arrays that can be used to write +// "total_bytes" number of bytes, each iovec writing one "buf". +std::vector<std::vector<struct iovec>> GenerateIovecs(uint64_t total_size, + void* buf, size_t buflen); + +// Sleep for at least the specified duration. Avoids glibc. +void SleepSafe(absl::Duration duration); + +// Returns bytes in 'n' megabytes. Used for readability. +uint64_t Megabytes(uint64_t n); + +// Predicate for checking that a value is within some tolerance of another +// value. Returns true iff current is in the range [target * (1 - tolerance), +// target * (1 + tolerance)]. +bool Equivalent(uint64_t current, uint64_t target, double tolerance); + +// Matcher wrapping the Equivalent predicate. +MATCHER_P2(EquivalentWithin, target, tolerance, + std::string(negation ? "Isn't" : "Is") + + ::absl::StrFormat(" within %.2f%% of the target of %zd bytes", + tolerance * 100, target)) { + if (target == 0) { + *result_listener << ::absl::StreamFormat("difference of infinity%%"); + } else { + int64_t delta = static_cast<int64_t>(arg) - static_cast<int64_t>(target); + double delta_percent = + static_cast<double>(delta) / static_cast<double>(target) * 100; + *result_listener << ::absl::StreamFormat("difference of %.2f%%", + delta_percent); + } + return Equivalent(arg, target, tolerance); +} + +void TestInit(int* argc, char*** argv); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_TEST_UTIL_H_ diff --git a/test/util/test_util_test.cc b/test/util/test_util_test.cc new file mode 100644 index 000000000..5889651d1 --- /dev/null +++ b/test/util/test_util_test.cc @@ -0,0 +1,250 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/test_util.h" + +#include <errno.h> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::AnyOf; +using ::testing::Gt; +using ::testing::IsEmpty; +using ::testing::Lt; +using ::testing::Not; +using ::testing::TypedEq; +using ::testing::UnorderedElementsAre; +using ::testing::UnorderedElementsAreArray; + +namespace gvisor { +namespace testing { + +namespace { + +TEST(KernelVersionParsing, ValidateParsing) { + KernelVersion v = ASSERT_NO_ERRNO_AND_VALUE( + ParseKernelVersion("4.18.10-1foo2-amd64 baz blah")); + ASSERT_TRUE(v == KernelVersion({4, 18, 10})); + + v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-1foo2-amd64")); + ASSERT_TRUE(v == KernelVersion({4, 18, 10})); + + v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-14-amd64")); + ASSERT_TRUE(v == KernelVersion({4, 18, 10})); + + v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10-amd64")); + ASSERT_TRUE(v == KernelVersion({4, 18, 10})); + + v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.18.10")); + ASSERT_TRUE(v == KernelVersion({4, 18, 10})); + + v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0.10")); + ASSERT_TRUE(v == KernelVersion({4, 0, 10})); + + v = ASSERT_NO_ERRNO_AND_VALUE(ParseKernelVersion("4.0")); + ASSERT_TRUE(v == KernelVersion({4, 0, 0})); + + ASSERT_THAT(ParseKernelVersion("4.a"), PosixErrorIs(EINVAL, ::testing::_)); + ASSERT_THAT(ParseKernelVersion("3"), PosixErrorIs(EINVAL, ::testing::_)); + ASSERT_THAT(ParseKernelVersion(""), PosixErrorIs(EINVAL, ::testing::_)); + ASSERT_THAT(ParseKernelVersion("version 3.3.10"), + PosixErrorIs(EINVAL, ::testing::_)); +} + +TEST(MatchersTest, SyscallSucceeds) { + EXPECT_THAT(0, SyscallSucceeds()); + EXPECT_THAT(0L, SyscallSucceeds()); + + errno = 0; + EXPECT_THAT(-1, SyscallSucceeds()); + EXPECT_THAT(-1L, SyscallSucceeds()); + + errno = ENOMEM; + EXPECT_THAT(-1, Not(SyscallSucceeds())); + EXPECT_THAT(-1L, Not(SyscallSucceeds())); +} + +TEST(MatchersTest, SyscallSucceedsWithValue) { + EXPECT_THAT(0, SyscallSucceedsWithValue(0)); + EXPECT_THAT(1, SyscallSucceedsWithValue(Lt(3))); + EXPECT_THAT(-1, Not(SyscallSucceedsWithValue(Lt(3)))); + EXPECT_THAT(4, Not(SyscallSucceedsWithValue(Lt(3)))); + + // Non-int -1 + EXPECT_THAT(-1L, Not(SyscallSucceedsWithValue(0))); + + // Non-int, truncates to -1 if converted to int, with expected value + EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(0xffffffffL)); + + // Non-int, truncates to -1 if converted to int, with monomorphic matcher + EXPECT_THAT(0xffffffffL, + SyscallSucceedsWithValue(TypedEq<long>(0xffffffffL))); + + // Non-int, truncates to -1 if converted to int, with polymorphic matcher + EXPECT_THAT(0xffffffffL, SyscallSucceedsWithValue(Gt(1))); +} + +TEST(MatchersTest, SyscallFails) { + EXPECT_THAT(0, Not(SyscallFails())); + EXPECT_THAT(0L, Not(SyscallFails())); + + errno = 0; + EXPECT_THAT(-1, Not(SyscallFails())); + EXPECT_THAT(-1L, Not(SyscallFails())); + + errno = ENOMEM; + EXPECT_THAT(-1, SyscallFails()); + EXPECT_THAT(-1L, SyscallFails()); +} + +TEST(MatchersTest, SyscallFailsWithErrno) { + EXPECT_THAT(0, Not(SyscallFailsWithErrno(EINVAL))); + EXPECT_THAT(0L, Not(SyscallFailsWithErrno(EINVAL))); + + errno = ENOMEM; + EXPECT_THAT(-1, Not(SyscallFailsWithErrno(EINVAL))); + EXPECT_THAT(-1L, Not(SyscallFailsWithErrno(EINVAL))); + + errno = EINVAL; + EXPECT_THAT(-1, SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(-1L, SyscallFailsWithErrno(EINVAL)); + + EXPECT_THAT(-1, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM))); + EXPECT_THAT(-1L, SyscallFailsWithErrno(AnyOf(EINVAL, ENOMEM))); + + std::vector<int> expected_errnos({EINVAL, ENOMEM}); + errno = ENOMEM; + EXPECT_THAT(-1, SyscallFailsWithErrno(ElementOf(expected_errnos))); + EXPECT_THAT(-1L, SyscallFailsWithErrno(ElementOf(expected_errnos))); +} + +TEST(AllBitwiseCombinationsTest, NoArguments) { + EXPECT_THAT(AllBitwiseCombinations(), IsEmpty()); +} + +TEST(AllBitwiseCombinationsTest, EmptyList) { + EXPECT_THAT(AllBitwiseCombinations(List<int>{}), IsEmpty()); +} + +TEST(AllBitwiseCombinationsTest, SingleElementList) { + EXPECT_THAT(AllBitwiseCombinations(List<int>{5}), UnorderedElementsAre(5)); +} + +TEST(AllBitwiseCombinationsTest, SingleList) { + EXPECT_THAT(AllBitwiseCombinations(List<int>{0, 1, 2, 4}), + UnorderedElementsAre(0, 1, 2, 4)); +} + +TEST(AllBitwiseCombinationsTest, MultipleLists) { + EXPECT_THAT( + AllBitwiseCombinations(List<int>{0, 1, 2, 3}, List<int>{0, 4, 8, 12}), + UnorderedElementsAreArray( + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})); +} + +TEST(RandomizeBuffer, Works) { + const std::vector<char> original(4096); + std::vector<char> buffer = original; + RandomizeBuffer(buffer.data(), buffer.size()); + EXPECT_NE(buffer, original); +} + +// Enable comparison of vectors of iovec arrays for the following test. +MATCHER_P(IovecsListEq, expected, "") { + if (arg.size() != expected.size()) { + *result_listener << "sizes are different (actual: " << arg.size() + << ", expected: " << expected.size() << ")"; + return false; + } + + for (uint64_t i = 0; i < expected.size(); ++i) { + const std::vector<struct iovec>& actual_iovecs = arg[i]; + const std::vector<struct iovec>& expected_iovecs = expected[i]; + if (actual_iovecs.size() != expected_iovecs.size()) { + *result_listener << "iovec array size at position " << i + << " is different (actual: " << actual_iovecs.size() + << ", expected: " << expected_iovecs.size() << ")"; + return false; + } + + for (uint64_t j = 0; j < expected_iovecs.size(); ++j) { + const struct iovec& actual_iov = actual_iovecs[j]; + const struct iovec& expected_iov = expected_iovecs[j]; + if (actual_iov.iov_base != expected_iov.iov_base) { + *result_listener << "iovecs in array " << i << " at position " << j + << " are different (expected iov_base: " + << expected_iov.iov_base + << ", got: " << actual_iov.iov_base << ")"; + return false; + } + if (actual_iov.iov_len != expected_iov.iov_len) { + *result_listener << "iovecs in array " << i << " at position " << j + << " are different (expected iov_len: " + << expected_iov.iov_len + << ", got: " << actual_iov.iov_len << ")"; + return false; + } + } + } + + return true; +} + +// Verify empty iovec list generation. +TEST(GenerateIovecs, EmptyList) { + std::vector<char> buffer = {'a', 'b', 'c'}; + + EXPECT_THAT(GenerateIovecs(0, buffer.data(), buffer.size()), + IovecsListEq(std::vector<std::vector<struct iovec>>())); +} + +// Verify generating a single array of only one, partial, iovec. +TEST(GenerateIovecs, OneArray) { + std::vector<char> buffer = {'a', 'b', 'c'}; + + std::vector<std::vector<struct iovec>> expected; + struct iovec iov = {}; + iov.iov_base = buffer.data(); + iov.iov_len = 2; + expected.push_back(std::vector<struct iovec>({iov})); + EXPECT_THAT(GenerateIovecs(2, buffer.data(), buffer.size()), + IovecsListEq(expected)); +} + +// Verify that it wraps around after IOV_MAX iovecs. +TEST(GenerateIovecs, WrapsAtIovMax) { + std::vector<char> buffer = {'a', 'b', 'c'}; + + std::vector<std::vector<struct iovec>> expected; + struct iovec iov = {}; + iov.iov_base = buffer.data(); + iov.iov_len = buffer.size(); + expected.emplace_back(); + for (int i = 0; i < IOV_MAX; ++i) { + expected[0].push_back(iov); + } + iov.iov_len = 1; + expected.push_back(std::vector<struct iovec>({iov})); + + EXPECT_THAT( + GenerateIovecs(IOV_MAX * buffer.size() + 1, buffer.data(), buffer.size()), + IovecsListEq(expected)); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/thread_util.h b/test/util/thread_util.h new file mode 100644 index 000000000..df09ac8cf --- /dev/null +++ b/test/util/thread_util.h @@ -0,0 +1,89 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_THREAD_UTIL_H_ +#define GVISOR_TEST_UTIL_THREAD_UTIL_H_ + +#include <pthread.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <functional> +#include <utility> + +#include "test/util/logging.h" + +namespace gvisor { +namespace testing { + +// ScopedThread is a minimal wrapper around pthreads. +// +// This is used in lieu of more complex mechanisms because it provides very +// predictable behavior (no messing with timers, etc.) The thread will +// automatically joined when it is destructed (goes out of scope), but can be +// joined manually as well. +class ScopedThread { + public: + // Constructs a thread that executes f exactly once. + explicit ScopedThread(std::function<void*()> f) : f_(std::move(f)) { + CreateThread(); + } + + explicit ScopedThread(const std::function<void()>& f) { + f_ = [=] { + f(); + return nullptr; + }; + CreateThread(); + } + + ScopedThread(const ScopedThread& other) = delete; + ScopedThread& operator=(const ScopedThread& other) = delete; + + // Joins the thread. + ~ScopedThread() { Join(); } + + // Waits until this thread has finished executing. Join is idempotent and may + // be called multiple times, however Join itself is not thread-safe. + void* Join() { + if (!joined_) { + TEST_PCHECK(pthread_join(pt_, &retval_) == 0); + joined_ = true; + } + return retval_; + } + + private: + void CreateThread() { + TEST_PCHECK_MSG( + pthread_create(&pt_, /* attr = */ nullptr, + +[](void* arg) -> void* { + return static_cast<ScopedThread*>(arg)->f_(); + }, + this) == 0, + "thread creation failed"); + } + + std::function<void*()> f_; + pthread_t pt_; + bool joined_ = false; + void* retval_ = nullptr; +}; + +inline pid_t gettid() { return syscall(SYS_gettid); } + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_THREAD_UTIL_H_ diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc new file mode 100644 index 000000000..681fafb69 --- /dev/null +++ b/test/util/timer_util.cc @@ -0,0 +1,27 @@ +// Copyright 2018 Google LLC +// +// 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 "test/util/timer_util.h" + +namespace gvisor { +namespace testing { + +absl::Time Now(clockid_t id) { + struct timespec now; + TEST_PCHECK(clock_gettime(id, &now) == 0); + return absl::TimeFromTimespec(now); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/timer_util.h b/test/util/timer_util.h new file mode 100644 index 000000000..9bdc51a57 --- /dev/null +++ b/test/util/timer_util.h @@ -0,0 +1,74 @@ +// Copyright 2018 Google LLC +// +// 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. + +#ifndef GVISOR_TEST_UTIL_TIMER_UTIL_H_ +#define GVISOR_TEST_UTIL_TIMER_UTIL_H_ + +#include <errno.h> +#include <sys/time.h> + +#include <functional> + +#include "gmock/gmock.h" +#include "absl/time/time.h" +#include "test/util/cleanup.h" +#include "test/util/logging.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// MonotonicTimer is a simple timer that uses a monotic clock. +class MonotonicTimer { + public: + MonotonicTimer() {} + absl::Duration Duration() { + struct timespec ts; + TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + return absl::TimeFromTimespec(ts) - start_; + } + + void Start() { + struct timespec ts; + TEST_CHECK(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + start_ = absl::TimeFromTimespec(ts); + } + + protected: + absl::Time start_; +}; + +// Sets the given itimer and returns a cleanup function that restores the +// previous itimer when it goes out of scope. +inline PosixErrorOr<Cleanup> ScopedItimer(int which, + struct itimerval const& new_value) { + struct itimerval old_value; + int rc = setitimer(which, &new_value, &old_value); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "setitimer failed"); + } + return Cleanup(std::function<void(void)>([which, old_value] { + EXPECT_THAT(setitimer(which, &old_value, nullptr), SyscallSucceeds()); + })); +} + +// Returns the current time. +absl::Time Now(clockid_t id); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_TIMER_UTIL_H_ |