diff options
Diffstat (limited to 'test/util')
51 files changed, 6173 insertions, 0 deletions
diff --git a/test/util/BUILD b/test/util/BUILD new file mode 100644 index 000000000..2a17c33ee --- /dev/null +++ b/test/util/BUILD @@ -0,0 +1,358 @@ +load("//tools:defs.bzl", "cc_library", "cc_test", "gbenchmark", "gtest", "select_system") + +package( + default_visibility = ["//:sandbox"], + licenses = ["notice"], +) + +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 = "eventfd_util", + testonly = 1, + hdrs = ["eventfd_util.h"], + deps = [ + ":file_descriptor", + ":posix_error", + ":save_util", + ], +) + +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", + 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", + gtest, + ], +) + +cc_test( + name = "proc_util_test", + size = "small", + srcs = ["proc_util_test.cc"], + deps = [ + ":proc_util", + ":test_main", + ":test_util", + 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", + gtest, + ], +) + +cc_test( + name = "fs_util_test", + size = "small", + srcs = ["fs_util_test.cc"], + deps = [ + ":fs_util", + ":posix_error", + ":temp_path", + ":test_main", + ":test_util", + gtest, + ], +) + +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", + gtest, + ], +) + +cc_library( + name = "save_util", + testonly = 1, + srcs = [ + "save_util.cc", + "save_util_linux.cc", + "save_util_other.cc", + ], + hdrs = ["save_util.h"], + defines = select_system(), +) + +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 = "platform_util", + testonly = 1, + srcs = ["platform_util.cc"], + hdrs = ["platform_util.h"], + deps = [":test_util"], +) + +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", + gtest, + ], +) + +cc_test( + name = "posix_error_test", + size = "small", + srcs = ["posix_error_test.cc"], + deps = [ + ":posix_error", + ":test_main", + gtest, + ], +) + +cc_library( + name = "pty_util", + testonly = 1, + srcs = ["pty_util.cc"], + hdrs = ["pty_util.h"], + deps = [ + ":file_descriptor", + ":posix_error", + ], +) + +cc_library( + name = "signal_util", + testonly = 1, + srcs = ["signal_util.cc"], + hdrs = ["signal_util.h"], + deps = [ + ":cleanup", + ":posix_error", + ":test_util", + 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", + gtest, + ], +) + +cc_library( + name = "test_util", + testonly = 1, + srcs = [ + "test_util.cc", + "test_util_impl.cc", + "test_util_runfiles.cc", + ], + hdrs = ["test_util.h"], + defines = select_system(), + deps = [ + ":fs_util", + ":logging", + ":posix_error", + ":save_util", + "@bazel_tools//tools/cpp/runfiles", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + gtest, + gbenchmark, + ], +) + +cc_library( + name = "thread_util", + testonly = 1, + hdrs = ["thread_util.h"], + deps = [":logging"], +) + +cc_library( + name = "time_util", + testonly = 1, + srcs = ["time_util.cc"], + hdrs = ["time_util.h"], + deps = [ + "@com_google_absl//absl/time", + ], +) + +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", + gtest, + ], +) + +cc_test( + name = "test_util_test", + size = "small", + srcs = ["test_util_test.cc"], + deps = [ + ":test_main", + ":test_util", + gtest, + ], +) + +cc_library( + name = "test_main", + testonly = 1, + srcs = ["test_main.cc"], + deps = [":test_util"], +) + +cc_library( + name = "epoll_util", + testonly = 1, + srcs = ["epoll_util.cc"], + hdrs = ["epoll_util.h"], + deps = [ + ":file_descriptor", + ":posix_error", + ":save_util", + gtest, + ], +) + +cc_library( + name = "rlimit_util", + testonly = 1, + srcs = ["rlimit_util.cc"], + hdrs = ["rlimit_util.h"], + deps = [ + ":cleanup", + ":logging", + ":posix_error", + ":test_util", + ], +) + +cc_library( + name = "uid_util", + testonly = 1, + srcs = ["uid_util.cc"], + hdrs = ["uid_util.h"], + deps = [ + ":posix_error", + ":save_util", + ], +) + +cc_library( + name = "temp_umask", + testonly = 1, + hdrs = ["temp_umask.h"], +) diff --git a/test/util/capability_util.cc b/test/util/capability_util.cc new file mode 100644 index 000000000..a1b994c45 --- /dev/null +++ b/test/util/capability_util.cc @@ -0,0 +1,81 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <iostream> + +#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)." + std::cerr << "clone(CLONE_NEWUSER) failed with EPERM" << std::endl; + 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)." + std::cerr << "clone(CLONE_NEWUSER) failed with EUSERS" << std::endl; + 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..bb9ea1fe5 --- /dev/null +++ b/test/util/capability_util.h @@ -0,0 +1,101 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 capabilities. + +#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..c76482ef4 --- /dev/null +++ b/test/util/cleanup.h @@ -0,0 +1,61 @@ +// Copyright 2018 The gVisor Authors. +// +// 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/epoll_util.cc b/test/util/epoll_util.cc new file mode 100644 index 000000000..2e5051468 --- /dev/null +++ b/test/util/epoll_util.cc @@ -0,0 +1,52 @@ +// Copyright 2018 The gVisor Authors. +// +// 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/epoll_util.h" + +#include <sys/epoll.h> + +#include "gmock/gmock.h" +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<FileDescriptor> NewEpollFD(int size) { + // "Since Linux 2.6.8, the size argument is ignored, but must be greater than + // zero." - epoll_create(2) + int fd = epoll_create(size); + MaybeSave(); + if (fd < 0) { + return PosixError(errno, "epoll_create"); + } + return FileDescriptor(fd); +} + +PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events, + uint64_t data) { + struct epoll_event event; + event.events = events; + event.data.u64 = data; + int rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, target_fd, &event); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "epoll_ctl"); + } + return NoError(); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/epoll_util.h b/test/util/epoll_util.h new file mode 100644 index 000000000..f233b37d5 --- /dev/null +++ b/test/util/epoll_util.h @@ -0,0 +1,36 @@ +// Copyright 2018 The gVisor Authors. +// +// 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_EPOLL_UTIL_H_ +#define GVISOR_TEST_UTIL_EPOLL_UTIL_H_ + +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// Returns a new epoll file descriptor. +PosixErrorOr<FileDescriptor> NewEpollFD(int size = 1); + +// Registers `target_fd` with the epoll instance represented by `epoll_fd` for +// the epoll events `events`. Events on `target_fd` will be indicated by setting +// data.u64 to `data` in the returned epoll_event. +PosixError RegisterEpollFD(int epoll_fd, int target_fd, int events, + uint64_t data); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_EPOLL_UTIL_H_ diff --git a/test/util/eventfd_util.h b/test/util/eventfd_util.h new file mode 100644 index 000000000..cb9ce829c --- /dev/null +++ b/test/util/eventfd_util.h @@ -0,0 +1,43 @@ +// Copyright 2019 The gVisor Authors. +// +// 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_EVENTFD_UTIL_H_ +#define GVISOR_TEST_UTIL_EVENTFD_UTIL_H_ + +#include <sys/eventfd.h> + +#include <cerrno> + +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" + +namespace gvisor { +namespace testing { + +// Returns a new eventfd with the given initial value and flags. +inline PosixErrorOr<FileDescriptor> NewEventFD(unsigned int initval = 0, + int flags = 0) { + int fd = eventfd(initval, flags); + MaybeSave(); + if (fd < 0) { + return PosixError(errno, "eventfd"); + } + return FileDescriptor(fd); +} + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_EVENTFD_UTIL_H_ diff --git a/test/util/file_descriptor.h b/test/util/file_descriptor.h new file mode 100644 index 000000000..fc5caa55b --- /dev/null +++ b/test/util/file_descriptor.h @@ -0,0 +1,134 @@ +// Copyright 2018 The gVisor Authors. +// +// 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..5418948fe --- /dev/null +++ b/test/util/fs_util.cc @@ -0,0 +1,633 @@ +// Copyright 2018 The gVisor Authors. +// +// 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<struct stat> Lstat(absl::string_view path) { + struct stat stat_buf; + int res = lstat(std::string(path).c_str(), &stat_buf); + if (res < 0) { + return PosixError(errno, absl::StrCat("lstat ", path)); + } + return stat_buf; +} + +PosixErrorOr<struct stat> Fstat(int fd) { + struct stat stat_buf; + int res = fstat(fd, &stat_buf); + if (res < 0) { + return PosixError(errno, absl::StrCat("fstat ", fd)); + } + return stat_buf; +} + +PosixErrorOr<bool> Exists(absl::string_view path) { + struct stat stat_buf; + int res = lstat(std::string(path).c_str(), &stat_buf); + if (res < 0) { + if (errno == ENOENT) { + return false; + } + return PosixError(errno, absl::StrCat("lstat ", path)); + } + return true; +} + +PosixErrorOr<bool> IsDirectory(absl::string_view path) { + ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Lstat(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 MknodAt(const FileDescriptor& dfd, absl::string_view path, int mode, + dev_t dev) { + int res = mknodat(dfd.get(), std::string(path).c_str(), mode, dev); + if (res < 0) { + return PosixError(errno, absl::StrCat("mknod ", path)); + } + + return NoError(); +} + +PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, + int flags) { + int res = unlinkat(dfd.get(), std::string(path).c_str(), flags); + if (res < 0) { + return PosixError(errno, absl::StrCat("unlink ", 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..8cdac23a1 --- /dev/null +++ b/test/util/fs_util.h @@ -0,0 +1,210 @@ +// Copyright 2018 The gVisor Authors. +// +// 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/file_descriptor.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// O_LARGEFILE as defined by Linux. glibc tries to be clever by setting it to 0 +// because "it isn't needed", even though Linux can return it via F_GETFL. +#if defined(__x86_64__) +constexpr int kOLargeFile = 00100000; +#elif defined(__aarch64__) +constexpr int kOLargeFile = 00400000; +#else +#error "Unknown architecture" +#endif + +// 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. If the path +// represents a symlink, it will be traversed. +PosixErrorOr<struct stat> Stat(absl::string_view path); + +// Returns a stat structure for the given path or an error. If the path +// represents a symlink, it will not be traversed. +PosixErrorOr<struct stat> Lstat(absl::string_view path); + +// Returns a stat struct for the given fd. +PosixErrorOr<struct stat> Fstat(int fd); + +// 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); + +// Create a special or ordinary file. +PosixError MknodAt(const FileDescriptor& dfd, absl::string_view path, int mode, + dev_t dev); + +// Unlink the file. +PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, + int flags); + +// 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 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 +// 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 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 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 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 +// 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..657b6a46e --- /dev/null +++ b/test/util/fs_util_test.cc @@ -0,0 +1,105 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <errno.h> + +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.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..5d5e76c46 --- /dev/null +++ b/test/util/logging.cc @@ -0,0 +1,97 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 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..589166fab --- /dev/null +++ b/test/util/logging.h @@ -0,0 +1,73 @@ +// Copyright 2018 The gVisor Authors. +// +// 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..e189b73e8 --- /dev/null +++ b/test/util/memory_util.h @@ -0,0 +1,147 @@ +// Copyright 2018 The gVisor Authors. +// +// 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); +} + +// Wrapper for mremap that returns a PosixErrorOr<>, since the return type of +// void* isn't directly compatible with SyscallSucceeds. +inline PosixErrorOr<void*> Mremap(void* old_address, size_t old_size, + size_t new_size, int flags, + void* new_address) { + void* rv = mremap(old_address, old_size, new_size, flags, new_address); + if (rv == MAP_FAILED) { + return PosixError(errno, "mremap failed"); + } + return rv; +} + +// Returns true if the page containing addr is mapped. +inline bool IsMapped(uintptr_t addr) { + int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)), + kPageSize, MS_ASYNC); + if (rv == 0) { + return true; + } + TEST_PCHECK_MSG(errno == ENOMEM, "msync failed with unexpected errno"); + return false; +} + +} // 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..09e2281eb --- /dev/null +++ b/test/util/mount_util.h @@ -0,0 +1,51 @@ +// Copyright 2018 The gVisor Authors. +// +// 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..8b676751b --- /dev/null +++ b/test/util/multiprocess_util.cc @@ -0,0 +1,173 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <asm/unistd.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 { + +namespace { + +// exec_fn wraps a variant of the exec family, e.g. execve or execveat. +PosixErrorOr<Cleanup> ForkAndExecHelper(const std::function<void()>& exec_fn, + 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(); + } + + // Call variant of exec function. + exec_fn(); + + 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); +} + +} // namespace + +PosixErrorOr<Cleanup> ForkAndExec(const std::string& filename, + const ExecveArray& argv, + const ExecveArray& envv, + const std::function<void()>& fn, pid_t* child, + int* execve_errno) { + char* const* argv_data = argv.get(); + char* const* envv_data = envv.get(); + const std::function<void()> exec_fn = [=] { + execve(filename.c_str(), argv_data, envv_data); + }; + return ForkAndExecHelper(exec_fn, fn, child, execve_errno); +} + +PosixErrorOr<Cleanup> ForkAndExecveat(const int32_t dirfd, + const std::string& pathname, + const ExecveArray& argv, + const ExecveArray& envv, const int flags, + const std::function<void()>& fn, + pid_t* child, int* execve_errno) { + char* const* argv_data = argv.get(); + char* const* envv_data = envv.get(); + const std::function<void()> exec_fn = [=] { + syscall(__NR_execveat, dirfd, pathname.c_str(), argv_data, envv_data, + flags); + }; + return ForkAndExecHelper(exec_fn, fn, child, execve_errno); +} + +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..2f3bf4a6f --- /dev/null +++ b/test/util/multiprocess_util.h @@ -0,0 +1,132 @@ +// Copyright 2018 The gVisor Authors. +// +// 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(); } + size_t get_size() { return str_.size(); } + + 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); +} + +// Equivalent to ForkAndExec, except using dirfd and flags with execveat. +PosixErrorOr<Cleanup> ForkAndExecveat(int32_t dirfd, + const std::string& pathname, + const ExecveArray& argv, + const ExecveArray& envv, int flags, + const std::function<void()>& fn, + pid_t* child, int* execve_errno); + +inline PosixErrorOr<Cleanup> ForkAndExecveat(int32_t dirfd, + const std::string& pathname, + const ExecveArray& argv, + const ExecveArray& envv, int flags, + pid_t* child, int* execve_errno) { + return ForkAndExecveat( + dirfd, pathname, argv, envv, flags, [] {}, 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/platform_util.cc b/test/util/platform_util.cc new file mode 100644 index 000000000..c9200d381 --- /dev/null +++ b/test/util/platform_util.cc @@ -0,0 +1,48 @@ +// Copyright 2020 The gVisor Authors. +// +// 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/platform_util.h" + +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +PlatformSupport PlatformSupport32Bit() { + if (GvisorPlatform() == Platform::kPtrace || + GvisorPlatform() == Platform::kKVM) { + return PlatformSupport::NotSupported; + } else { + return PlatformSupport::Allowed; + } +} + +PlatformSupport PlatformSupportAlignmentCheck() { + return PlatformSupport::Allowed; +} + +PlatformSupport PlatformSupportMultiProcess() { + return PlatformSupport::Allowed; +} + +PlatformSupport PlatformSupportInt3() { + if (GvisorPlatform() == Platform::kKVM) { + return PlatformSupport::NotSupported; + } else { + return PlatformSupport::Allowed; + } +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/platform_util.h b/test/util/platform_util.h new file mode 100644 index 000000000..28cc92371 --- /dev/null +++ b/test/util/platform_util.h @@ -0,0 +1,56 @@ +// Copyright 2020 The gVisor Authors. +// +// 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_PLATFORM_UTIL_H_ +#define GVISOR_TEST_UTIL_PLATFORM_UTIL_H_ + +namespace gvisor { +namespace testing { + +// PlatformSupport is a generic enumeration of classes of support. +// +// It is up to the individual functions and callers to agree on the precise +// definition for each case. The document here generally refers to 32-bit +// as an example. Many cases will use only NotSupported and Allowed. +enum class PlatformSupport { + // The feature is not supported on the current platform. + // + // In the case of 32-bit, this means that calls will generally be interpreted + // as 64-bit calls, and there is no support for 32-bit binaries, long calls, + // etc. This usually means that the underlying implementation just pretends + // that 32-bit doesn't exist. + NotSupported, + + // Calls will be ignored by the kernel with a fixed error. + Ignored, + + // Calls will result in a SIGSEGV or similar fault. + Segfault, + + // The feature is supported as expected. + // + // In the case of 32-bit, this means that the system call or far call will be + // handled properly. + Allowed, +}; + +PlatformSupport PlatformSupport32Bit(); +PlatformSupport PlatformSupportAlignmentCheck(); +PlatformSupport PlatformSupportMultiProcess(); +PlatformSupport PlatformSupportInt3(); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_PLATFORM_UTL_H_ diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc new file mode 100644 index 000000000..cebf7e0ac --- /dev/null +++ b/test/util/posix_error.cc @@ -0,0 +1,98 @@ +// Copyright 2018 The gVisor Authors. +// +// 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] = {}; + + auto res = strerror_r(errno_, strerrno_buf, sizeof(strerrno_buf)); + +// The GNU version of strerror_r always returns a non-null char* pointing to a +// buffer containing the stringified errno; the XSI version returns a positive +// errno which indicates the result of writing the stringified errno into the +// supplied buffer. The gymnastics below are needed to support both. +#ifndef _GNU_SOURCE + if (res != 0) { + ret = absl::StrCat("PosixError(errno=", errno_, " strerror_r FAILED(", ret, + "))"); + } else { + ret = absl::StrCat("PosixError(errno=", errno_, " ", strerrno_buf, ")"); + } +#else + ret = absl::StrCat("PosixError(errno=", errno_, " ", res, ")"); +#endif + + 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)) { + return false; + } + + if (!message_matcher_.Matches(error.error_message())) { + 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..ad666bce0 --- /dev/null +++ b/test/util/posix_error.h @@ -0,0 +1,462 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 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: + // A PosixErrorOr will check fail if it is constructed with NoError(). + PosixErrorOr(const PosixError& error); + PosixErrorOr(const T& value); + PosixErrorOr(T&& value); + + 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(). + PosixError error() const; + + // Returns this->error().error_message(); + std::string error_message() const; + + // Returns true if this PosixErrorOr contains some T. + bool ok() const; + + // Returns a reference to our current value, or CHECK-fails if !this->ok(). + const T& ValueOrDie() const&; + T& ValueOrDie() &; + 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: + int errno_value() const; + absl::variant<T, PosixError> value_; + + friend class PosixErrorIsMatcherCommonImpl; +}; + +template <typename T> +PosixErrorOr<T>::PosixErrorOr(const PosixError& error) : value_(error) { + TEST_CHECK_MSG( + !error.ok(), + "Constructing PosixErrorOr with NoError, eg. errno 0 is not allowed."); +} + +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> +PosixError PosixErrorOr<T>::error() const { + if (!absl::holds_alternative<PosixError>(value_)) { + return PosixError(); + } + return absl::get<PosixError>(value_); +} + +template <typename T> +int PosixErrorOr<T>::errno_value() const { + return error().errno_value(); +} + +template <typename T> +std::string PosixErrorOr<T>::error_message() const { + return error().error_message(); +} + +template <typename T> +bool PosixErrorOr<T>::ok() const { + return absl::holds_alternative<T>(value_); +} + +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_); +} + +template <typename T> +const T&& PosixErrorOr<T>::ValueOrDie() const&& { + TEST_CHECK(absl::holds_alternative<T>(value_)); + return std::move(absl::get<T>(value_)); +} + +template <typename T> +T&& PosixErrorOr<T>::ValueOrDie() && { + TEST_CHECK(absl::holds_alternative<T>(value_)); + return std::move(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 { + // We can't extract the value if it doesn't contain one. + if (!actual_value.ok()) { + 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(); + *listener << "has a value " + << ::testing::PrintToString(actual_value.ValueOrDie()); + + if (!inner_explanation.empty()) { + *listener << " " << 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; + + template <typename T> + bool MatchAndExplain(const PosixErrorOr<T>& error_or, + ::testing::MatchResultListener* result_listener) const { + if (error_or.ok()) { + *result_listener << "has a value " + << ::testing::PrintToString(error_or.ValueOrDie()); + return false; + } + + return MatchAndExplain(error_or.error(), result_listener); + } + + 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, 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..bf9465abb --- /dev/null +++ b/test/util/posix_error_test.cc @@ -0,0 +1,46 @@ +// Copyright 2018 The gVisor Authors. +// +// 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_TRUE(err.ok()); + 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..34d636ba9 --- /dev/null +++ b/test/util/proc_util.cc @@ -0,0 +1,107 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <iostream> +#include <vector> + +#include "absl/strings/ascii.h" +#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 = {}; + + // Limit splitting to 6 parts so that if there is a file path and it contains + // spaces, the file path is not split. + std::vector<std::string> parts = + absl::StrSplit(line, absl::MaxSplits(' ', 5), absl::SkipEmpty()); + + // parts.size() should be 6 if there is a file name specified, and 5 + // otherwise. + if (parts.size() < 5) { + 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. However, absl::StrSplit retained the whitespace + // between the inode number and the filename. + map_entry.filename = + std::string(absl::StripLeadingAsciiWhitespace(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) { + std::cout << "line: " << l << std::endl; + 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..af209a51e --- /dev/null +++ b/test/util/proc_util.h @@ -0,0 +1,150 @@ +// Copyright 2018 The gVisor Authors. +// +// 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/proc_util_test.cc b/test/util/proc_util_test.cc new file mode 100644 index 000000000..71dd2355e --- /dev/null +++ b/test/util/proc_util_test.cc @@ -0,0 +1,81 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/util/test_util.h" + +using ::testing::IsEmpty; + +namespace gvisor { +namespace testing { + +namespace { + +TEST(ParseProcMapsLineTest, WithoutFilename) { + auto entry = ASSERT_NO_ERRNO_AND_VALUE( + ParseProcMapsLine("2ab4f00b7000-2ab4f00b9000 r-xp 00000000 00:00 0 ")); + EXPECT_EQ(entry.start, 0x2ab4f00b7000); + EXPECT_EQ(entry.end, 0x2ab4f00b9000); + EXPECT_TRUE(entry.readable); + EXPECT_FALSE(entry.writable); + EXPECT_TRUE(entry.executable); + EXPECT_TRUE(entry.priv); + EXPECT_EQ(entry.offset, 0); + EXPECT_EQ(entry.major, 0); + EXPECT_EQ(entry.minor, 0); + EXPECT_EQ(entry.inode, 0); + EXPECT_THAT(entry.filename, IsEmpty()); +} + +TEST(ParseProcMapsLineTest, WithFilename) { + auto entry = ASSERT_NO_ERRNO_AND_VALUE( + ParseProcMapsLine("00407000-00408000 rw-p 00006000 00:0e 10 " + " /bin/cat")); + EXPECT_EQ(entry.start, 0x407000); + EXPECT_EQ(entry.end, 0x408000); + EXPECT_TRUE(entry.readable); + EXPECT_TRUE(entry.writable); + EXPECT_FALSE(entry.executable); + EXPECT_TRUE(entry.priv); + EXPECT_EQ(entry.offset, 0x6000); + EXPECT_EQ(entry.major, 0); + EXPECT_EQ(entry.minor, 0x0e); + EXPECT_EQ(entry.inode, 10); + EXPECT_EQ(entry.filename, "/bin/cat"); +} + +TEST(ParseProcMapsLineTest, WithFilenameContainingSpaces) { + auto entry = ASSERT_NO_ERRNO_AND_VALUE( + ParseProcMapsLine("7f26b3b12000-7f26b3b13000 rw-s 00000000 00:05 1432484 " + " /dev/zero (deleted)")); + EXPECT_EQ(entry.start, 0x7f26b3b12000); + EXPECT_EQ(entry.end, 0x7f26b3b13000); + EXPECT_TRUE(entry.readable); + EXPECT_TRUE(entry.writable); + EXPECT_FALSE(entry.executable); + EXPECT_FALSE(entry.priv); + EXPECT_EQ(entry.offset, 0); + EXPECT_EQ(entry.major, 0); + EXPECT_EQ(entry.minor, 0x05); + EXPECT_EQ(entry.inode, 1432484); + EXPECT_EQ(entry.filename, "/dev/zero (deleted)"); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/pty_util.cc b/test/util/pty_util.cc new file mode 100644 index 000000000..c01f916aa --- /dev/null +++ b/test/util/pty_util.cc @@ -0,0 +1,53 @@ +// Copyright 2019 The gVisor Authors. +// +// 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/pty_util.h" + +#include <sys/ioctl.h> +#include <termios.h> + +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<FileDescriptor> OpenSlave(const FileDescriptor& master) { + PosixErrorOr<int> n = SlaveID(master); + if (!n.ok()) { + return PosixErrorOr<FileDescriptor>(n.error()); + } + return Open(absl::StrCat("/dev/pts/", n.ValueOrDie()), O_RDWR | O_NONBLOCK); +} + +PosixErrorOr<int> SlaveID(const FileDescriptor& master) { + // Get pty index. + int n; + int ret = ioctl(master.get(), TIOCGPTN, &n); + if (ret < 0) { + return PosixError(errno, "ioctl(TIOCGPTN) failed"); + } + + // Unlock pts. + int unlock = 0; + ret = ioctl(master.get(), TIOCSPTLCK, &unlock); + if (ret < 0) { + return PosixError(errno, "ioctl(TIOSPTLCK) failed"); + } + + return n; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/pty_util.h b/test/util/pty_util.h new file mode 100644 index 000000000..0722da379 --- /dev/null +++ b/test/util/pty_util.h @@ -0,0 +1,33 @@ +// Copyright 2019 The gVisor Authors. +// +// 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_PTY_UTIL_H_ +#define GVISOR_TEST_UTIL_PTY_UTIL_H_ + +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// Opens the slave end of the passed master as R/W and nonblocking. +PosixErrorOr<FileDescriptor> OpenSlave(const FileDescriptor& master); + +// Get the number of the slave end of the master. +PosixErrorOr<int> SlaveID(const FileDescriptor& master); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_PTY_UTIL_H_ diff --git a/test/util/rlimit_util.cc b/test/util/rlimit_util.cc new file mode 100644 index 000000000..d7bfc1606 --- /dev/null +++ b/test/util/rlimit_util.cc @@ -0,0 +1,45 @@ +// Copyright 2019 The gVisor Authors. +// +// 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/rlimit_util.h" + +#include <sys/resource.h> + +#include <cerrno> + +#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 { + +PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval) { + struct rlimit old_rlim; + if (getrlimit(resource, &old_rlim) != 0) { + return PosixError(errno, "getrlimit failed"); + } + struct rlimit new_rlim = old_rlim; + new_rlim.rlim_cur = newval; + if (setrlimit(resource, &new_rlim) != 0) { + return PosixError(errno, "setrlimit failed"); + } + return Cleanup([resource, old_rlim] { + TEST_PCHECK(setrlimit(resource, &old_rlim) == 0); + }); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/rlimit_util.h b/test/util/rlimit_util.h new file mode 100644 index 000000000..873252a32 --- /dev/null +++ b/test/util/rlimit_util.h @@ -0,0 +1,32 @@ +// Copyright 2019 The gVisor Authors. +// +// 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_RLIMIT_UTIL_H_ +#define GVISOR_TEST_UTIL_RLIMIT_UTIL_H_ + +#include <sys/resource.h> +#include <sys/time.h> + +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval); + +} // namespace testing +} // namespace gvisor +#endif // GVISOR_TEST_UTIL_RLIMIT_UTIL_H_ diff --git a/test/util/save_util.cc b/test/util/save_util.cc new file mode 100644 index 000000000..384d626f0 --- /dev/null +++ b/test/util/save_util.cc @@ -0,0 +1,71 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <unistd.h> + +#include <atomic> +#include <cerrno> + +#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST" + +namespace gvisor { +namespace testing { +namespace { + +enum class CooperativeSaveMode { + kUnknown = 0, // cooperative_save_mode is statically-initialized to 0 + kAvailable, + kNotAvailable, +}; + +std::atomic<CooperativeSaveMode> cooperative_save_mode; + +bool CooperativeSaveEnabled() { + auto mode = cooperative_save_mode.load(); + if (mode == CooperativeSaveMode::kUnknown) { + mode = (getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr) + ? CooperativeSaveMode::kAvailable + : CooperativeSaveMode::kNotAvailable; + cooperative_save_mode.store(mode); + } + return mode == CooperativeSaveMode::kAvailable; +} + +std::atomic<int> save_disable; + +} // namespace + +DisableSave::DisableSave() { save_disable++; } + +DisableSave::~DisableSave() { reset(); } + +void DisableSave::reset() { + if (!reset_) { + reset_ = true; + save_disable--; + } +} + +namespace internal { +bool ShouldSave() { + return CooperativeSaveEnabled() && (save_disable.load() == 0); +} +} // namespace internal + +} // namespace testing +} // namespace gvisor diff --git a/test/util/save_util.h b/test/util/save_util.h new file mode 100644 index 000000000..bddad6120 --- /dev/null +++ b/test/util/save_util.h @@ -0,0 +1,52 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 internal { +bool ShouldSave(); +} // namespace internal + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_SAVE_UTIL_H_ diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc new file mode 100644 index 000000000..d0aea8e6a --- /dev/null +++ b/test/util/save_util_linux.cc @@ -0,0 +1,49 @@ +// Copyright 2019 The gVisor Authors. +// +// 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. + +#ifdef __linux__ + +#include <errno.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "test/util/save_util.h" + +#if defined(__x86_64__) || defined(__i386__) +#define SYS_TRIGGER_SAVE SYS_create_module +#elif defined(__aarch64__) +#define SYS_TRIGGER_SAVE SYS_finit_module +#else +#error "Unknown architecture" +#endif + +namespace gvisor { +namespace testing { + +void MaybeSave() { + if (internal::ShouldSave()) { + int orig_errno = errno; + // We use it to trigger saving the sentry state + // when this syscall is called. + // Notice: this needs to be a valid syscall + // that is not used in any of the syscall tests. + syscall(SYS_TRIGGER_SAVE, nullptr, 0); + errno = orig_errno; + } +} + +} // namespace testing +} // namespace gvisor + +#endif diff --git a/test/util/save_util_other.cc b/test/util/save_util_other.cc new file mode 100644 index 000000000..931af2c29 --- /dev/null +++ b/test/util/save_util_other.cc @@ -0,0 +1,27 @@ +// Copyright 2019 The gVisor Authors. +// +// 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 __linux__ + +namespace gvisor { +namespace testing { + +void MaybeSave() { + // Saving is never available in a non-linux environment. +} + +} // namespace testing +} // namespace gvisor + +#endif diff --git a/test/util/signal_util.cc b/test/util/signal_util.cc new file mode 100644 index 000000000..5ee95ee80 --- /dev/null +++ b/test/util/signal_util.cc @@ -0,0 +1,104 @@ +// Copyright 2018 The gVisor Authors. +// +// 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..e7b66aa51 --- /dev/null +++ b/test/util/signal_util.h @@ -0,0 +1,107 @@ +// Copyright 2018 The gVisor Authors. +// +// 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_t* ctx) { + // Skip the bad instruction above. + // + // The encoding is 0x48 0xab 0x00. + ctx->uc_mcontext.gregs[REG_RIP] += 3; +} +#elif __aarch64__ +inline void Fault() { + // Zero and dereference x0. + asm("mov xzr, x0\r\n" + "str xzr, [x0]\r\n" + : + : + : "x0"); +} + +inline void FixupFault(ucontext_t* ctx) { + // Skip the bad instruction above. + ctx->uc_mcontext.pc += 4; +} +#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..e1bdee7fd --- /dev/null +++ b/test/util/temp_path.cc @@ -0,0 +1,164 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <iostream> + +#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()) { + std::cerr << path << ": failed to delete " << undeleted_dirs + << " directories and " << undeleted_files + << " files: " << status << std::endl; + } + } +} + +} // 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() { + // Note that TEST_TMPDIR is guaranteed to be set. + 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 { + // CreateWithContents 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..9e5ac11f4 --- /dev/null +++ b/test/util/temp_path.h @@ -0,0 +1,135 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 const dir); + +// 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 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/temp_umask.h b/test/util/temp_umask.h new file mode 100644 index 000000000..e7de84a54 --- /dev/null +++ b/test/util/temp_umask.h @@ -0,0 +1,39 @@ +// Copyright 2018 The gVisor Authors. +// +// 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_UMASK_H_ +#define GVISOR_TEST_UTIL_TEMP_UMASK_H_ + +#include <sys/stat.h> +#include <sys/types.h> + +namespace gvisor { +namespace testing { + +class TempUmask { + public: + // Sets the process umask to `mask`. + explicit TempUmask(mode_t mask) : old_mask_(umask(mask)) {} + + // Sets the process umask to its previous value. + ~TempUmask() { umask(old_mask_); } + + private: + mode_t old_mask_; +}; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_TEMP_UMASK_H_ diff --git a/test/util/test_main.cc b/test/util/test_main.cc new file mode 100644 index 000000000..1f389e58f --- /dev/null +++ b/test/util/test_main.cc @@ -0,0 +1,20 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 gvisor::testing::RunAllTests(); +} diff --git a/test/util/test_util.cc b/test/util/test_util.cc new file mode 100644 index 000000000..8a037f45f --- /dev/null +++ b/test/util/test_util.cc @@ -0,0 +1,233 @@ +// Copyright 2018 The gVisor Authors. +// +// 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/types.h> +#include <sys/uio.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include <ctime> +#include <iostream> +#include <vector> + +#include "absl/base/attributes.h" +#include "absl/flags/flag.h" // IWYU pragma: keep +#include "absl/flags/parse.h" // IWYU pragma: keep +#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 { + +constexpr char kGvisorNetwork[] = "GVISOR_NETWORK"; +constexpr char kGvisorVfs[] = "GVISOR_VFS"; + +bool IsRunningOnGvisor() { return GvisorPlatform() != Platform::kNative; } + +const std::string GvisorPlatform() { + // Set by runner.go. + const char* env = getenv(kTestOnGvisor); + if (!env) { + return Platform::kNative; + } + return std::string(env); +} + +bool IsRunningWithHostinet() { + const char* env = getenv(kGvisorNetwork); + return env && strcmp(env, "host") == 0; +} + +bool IsRunningWithVFS1() { + const char* env = getenv(kGvisorVfs); + if (env == nullptr) { + // If not set, it's running on Linux. + return false; + } + return strcmp(env, "VFS1") == 0; +} + +// 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)) + +CPUVendor GetCPUVendor() { + uint32_t eax, ebx, ecx, edx; + std::string vendor_str; + // Get vendor 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; +} +#endif // defined(__x86_64__) + +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); +} + +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; +} + +uint64_t Megabytes(uint64_t n) { + // Overflow check, upper 20 bits in n shouldn't be set. + TEST_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); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/test_util.h b/test/util/test_util.h new file mode 100644 index 000000000..109078fc7 --- /dev/null +++ b/test/util/test_util.h @@ -0,0 +1,784 @@ +// Copyright 2018 The gVisor Authors. +// +// 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: +// +// if (GvisorPlatform() == Platform::kPtrace) { +// ... +// } +// +// SetupGvisorDeathTest ensures that signal handling does not interfere with +/// tests that rely on fatal signals. +// +// 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 "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 { + +constexpr char kTestOnGvisor[] = "TEST_ON_GVISOR"; + +// 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) GTEST_SKIP() << #expr; \ + } while (0) + +// Platform contains platform names. +namespace Platform { +constexpr char kNative[] = "native"; +constexpr char kPtrace[] = "ptrace"; +constexpr char kKVM[] = "kvm"; +constexpr char kFuchsia[] = "fuchsia"; +} // namespace Platform + +bool IsRunningOnGvisor(); +const std::string GvisorPlatform(); +bool IsRunningWithHostinet(); +// TODO(gvisor.dev/issue/1624): Delete once VFS1 is gone. +bool IsRunningWithVFS1(); + +#ifdef __linux__ +void SetupGvisorDeathTest(); +#endif + +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 { + +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 " << PosixError(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 " << PosixError(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 << PosixError(expected_); + } + + void DescribeNegationTo(::std::ostream* const os) const override { + *os << "not " << PosixError(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); + +// 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); +} + +// Returns the absolute path to the a data dependency. 'path' is the runfile +// location relative to workspace root. +#ifdef __linux__ +std::string RunfilePath(std::string path); +#endif + +void TestInit(int* argc, char*** argv); +int RunAllTests(void); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_TEST_UTIL_H_ diff --git a/test/util/test_util_impl.cc b/test/util/test_util_impl.cc new file mode 100644 index 000000000..7e1ad9e66 --- /dev/null +++ b/test/util/test_util_impl.cc @@ -0,0 +1,52 @@ +// Copyright 2019 The gVisor Authors. +// +// 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 <signal.h> + +#include "gtest/gtest.h" +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "benchmark/benchmark.h" +#include "test/util/logging.h" + +extern bool FLAGS_benchmark_list_tests; +extern std::string FLAGS_benchmark_filter; + +namespace gvisor { +namespace testing { + +void SetupGvisorDeathTest() {} + +void TestInit(int* argc, char*** argv) { + ::testing::InitGoogleTest(argc, *argv); + benchmark::Initialize(argc, *argv); + ::absl::ParseCommandLine(*argc, *argv); + + // 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); +} + +int RunAllTests() { + if (FLAGS_benchmark_list_tests || FLAGS_benchmark_filter != ".") { + benchmark::RunSpecifiedBenchmarks(); + return 0; + } else { + return RUN_ALL_TESTS(); + } +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/test_util_runfiles.cc b/test/util/test_util_runfiles.cc new file mode 100644 index 000000000..694d21692 --- /dev/null +++ b/test/util/test_util_runfiles.cc @@ -0,0 +1,50 @@ +// Copyright 2019 The gVisor Authors. +// +// 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 __fuchsia__ + +#include <iostream> +#include <string> + +#include "test/util/fs_util.h" +#include "test/util/test_util.h" +#include "tools/cpp/runfiles/runfiles.h" + +namespace gvisor { +namespace testing { + +std::string RunfilePath(std::string path) { + static const bazel::tools::cpp::runfiles::Runfiles* const runfiles = [] { + std::string error; + auto* runfiles = + bazel::tools::cpp::runfiles::Runfiles::CreateForTest(&error); + if (runfiles == nullptr) { + std::cerr << "Unable to find runfiles: " << error << std::endl; + } + return runfiles; + }(); + + if (!runfiles) { + // Can't find runfiles? This probably won't work, but __main__/path is our + // best guess. + return JoinPath("__main__", path); + } + + return runfiles->Rlocation(JoinPath("__main__", path)); +} + +} // namespace testing +} // namespace gvisor + +#endif // __fuchsia__ diff --git a/test/util/test_util_test.cc b/test/util/test_util_test.cc new file mode 100644 index 000000000..f42100374 --- /dev/null +++ b/test/util/test_util_test.cc @@ -0,0 +1,251 @@ +// Copyright 2018 The gVisor Authors. +// +// 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..923c4fe10 --- /dev/null +++ b/test/util/thread_util.h @@ -0,0 +1,93 @@ +// Copyright 2018 The gVisor Authors. +// +// 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> +#ifdef __linux__ +#include <sys/syscall.h> +#endif +#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; +}; + +#ifdef __linux__ +inline pid_t gettid() { return syscall(SYS_gettid); } +#endif + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_THREAD_UTIL_H_ diff --git a/test/util/time_util.cc b/test/util/time_util.cc new file mode 100644 index 000000000..1ddfbfc9c --- /dev/null +++ b/test/util/time_util.cc @@ -0,0 +1,41 @@ +// Copyright 2019 The gVisor Authors. +// +// 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/time_util.h" + +#include <sys/syscall.h> +#include <unistd.h> + +#include "absl/time/time.h" + +namespace gvisor { +namespace testing { + +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; + } + } +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/time_util.h b/test/util/time_util.h new file mode 100644 index 000000000..f3ddc9fde --- /dev/null +++ b/test/util/time_util.h @@ -0,0 +1,29 @@ +// Copyright 2019 The gVisor Authors. +// +// 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_TIME_UTIL_H_ +#define GVISOR_TEST_UTIL_TIME_UTIL_H_ + +#include "absl/time/time.h" + +namespace gvisor { +namespace testing { + +// Sleep for at least the specified duration. Avoids glibc. +void SleepSafe(absl::Duration duration); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_TIME_UTIL_H_ diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc new file mode 100644 index 000000000..43a26b0d3 --- /dev/null +++ b/test/util/timer_util.cc @@ -0,0 +1,27 @@ +// Copyright 2018 The gVisor Authors. +// +// 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..31aea4fc6 --- /dev/null +++ b/test/util/timer_util.h @@ -0,0 +1,74 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 monotonic 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_ diff --git a/test/util/uid_util.cc b/test/util/uid_util.cc new file mode 100644 index 000000000..b131b4b99 --- /dev/null +++ b/test/util/uid_util.cc @@ -0,0 +1,44 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 "test/util/save_util.h" + +namespace gvisor { +namespace testing { + +PosixErrorOr<bool> IsRoot() { + uid_t ruid, euid, suid; + int rc = getresuid(&ruid, &euid, &suid); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "getresuid"); + } + if (ruid != 0 || euid != 0 || suid != 0) { + return false; + } + gid_t rgid, egid, sgid; + rc = getresgid(&rgid, &egid, &sgid); + MaybeSave(); + if (rc < 0) { + return PosixError(errno, "getresgid"); + } + if (rgid != 0 || egid != 0 || sgid != 0) { + return false; + } + return true; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/uid_util.h b/test/util/uid_util.h new file mode 100644 index 000000000..2cd387fb0 --- /dev/null +++ b/test/util/uid_util.h @@ -0,0 +1,29 @@ +// Copyright 2018 The gVisor Authors. +// +// 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_SYSCALLS_UID_UTIL_H_ +#define GVISOR_TEST_SYSCALLS_UID_UTIL_H_ + +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// Returns true if the caller's real/effective/saved user/group IDs are all 0. +PosixErrorOr<bool> IsRoot(); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_UID_UTIL_H_ |