diff --git a/envoy/api/os_sys_calls.h b/envoy/api/os_sys_calls.h index 28a591307443b..6a26a19edc70a 100644 --- a/envoy/api/os_sys_calls.h +++ b/envoy/api/os_sys_calls.h @@ -149,6 +149,11 @@ class OsSysCalls { */ virtual SysCallIntResult stat(const char* pathname, struct stat* buf) PURE; + /** + * @see man 2 fstat + */ + virtual SysCallIntResult fstat(os_fd_t fd, struct stat* buf) PURE; + /** * @see man 2 setsockopt */ diff --git a/source/common/api/posix/os_sys_calls_impl.cc b/source/common/api/posix/os_sys_calls_impl.cc index 3afac06a4c685..a142f949a61e7 100644 --- a/source/common/api/posix/os_sys_calls_impl.cc +++ b/source/common/api/posix/os_sys_calls_impl.cc @@ -194,6 +194,11 @@ SysCallIntResult OsSysCallsImpl::stat(const char* pathname, struct stat* buf) { return {rc, rc != -1 ? 0 : errno}; } +SysCallIntResult OsSysCallsImpl::fstat(os_fd_t fd, struct stat* buf) { + const int rc = ::fstat(fd, buf); + return {rc, rc != -1 ? 0 : errno}; +} + SysCallIntResult OsSysCallsImpl::setsockopt(os_fd_t sockfd, int level, int optname, const void* optval, socklen_t optlen) { const int rc = ::setsockopt(sockfd, level, optname, optval, optlen); diff --git a/source/common/api/posix/os_sys_calls_impl.h b/source/common/api/posix/os_sys_calls_impl.h index 61a79c8aab063..0c4491eab7996 100644 --- a/source/common/api/posix/os_sys_calls_impl.h +++ b/source/common/api/posix/os_sys_calls_impl.h @@ -35,6 +35,7 @@ class OsSysCallsImpl : public OsSysCalls { SysCallPtrResult mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) override; SysCallIntResult stat(const char* pathname, struct stat* buf) override; + SysCallIntResult fstat(os_fd_t fd, struct stat* buf) override; SysCallIntResult setsockopt(os_fd_t sockfd, int level, int optname, const void* optval, socklen_t optlen) override; SysCallIntResult getsockopt(os_fd_t sockfd, int level, int optname, void* optval, diff --git a/source/common/api/win32/os_sys_calls_impl.cc b/source/common/api/win32/os_sys_calls_impl.cc index 1a630b5d9c6b6..69502f12cff28 100644 --- a/source/common/api/win32/os_sys_calls_impl.cc +++ b/source/common/api/win32/os_sys_calls_impl.cc @@ -114,8 +114,14 @@ SysCallIntResult OsSysCallsImpl::ioctl(os_fd_t sockfd, unsigned long control_cod } SysCallIntResult OsSysCallsImpl::close(os_fd_t fd) { - const int rc = ::closesocket(fd); - return {rc, rc != -1 ? 0 : ::WSAGetLastError()}; + int rc = ::closesocket(fd); + const int socket_err = ::WSAGetLastError(); + if (rc == -1) { + // If `closesocket` failed, maybe the descriptor was a file. + rc = ::close(fd); + } + // Assume the descriptor was a socket, and return the error from that one, if both failed. + return {rc, rc != -1 ? 0 : socket_err}; } SysCallSizeResult OsSysCallsImpl::writev(os_fd_t fd, const iovec* iov, int num_iov) { @@ -226,6 +232,11 @@ SysCallIntResult OsSysCallsImpl::stat(const char* pathname, struct stat* buf) { return {rc, rc != -1 ? 0 : errno}; } +SysCallIntResult OsSysCallsImpl::fstat(os_fd_t fd, struct stat* buf) { + const int rc = ::fstat(fd, buf); + return {rc, rc != -1 ? 0 : errno}; +} + SysCallIntResult OsSysCallsImpl::setsockopt(os_fd_t sockfd, int level, int optname, const void* optval, socklen_t optlen) { const int rc = ::setsockopt(sockfd, level, optname, static_cast(optval), optlen); diff --git a/source/common/api/win32/os_sys_calls_impl.h b/source/common/api/win32/os_sys_calls_impl.h index 44bc4993709f8..2afa41dd2ce3b 100644 --- a/source/common/api/win32/os_sys_calls_impl.h +++ b/source/common/api/win32/os_sys_calls_impl.h @@ -36,6 +36,7 @@ class OsSysCallsImpl : public OsSysCalls { SysCallPtrResult mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) override; SysCallIntResult stat(const char* pathname, struct stat* buf) override; + SysCallIntResult fstat(os_fd_t fd, struct stat* buf) override; SysCallIntResult setsockopt(os_fd_t sockfd, int level, int optname, const void* optval, socklen_t optlen) override; SysCallIntResult getsockopt(os_fd_t sockfd, int level, int optname, void* optval, diff --git a/test/common/api/os_sys_calls_test.cc b/test/common/api/os_sys_calls_test.cc index 5748b257ac791..432a43b737e02 100644 --- a/test/common/api/os_sys_calls_test.cc +++ b/test/common/api/os_sys_calls_test.cc @@ -1,9 +1,73 @@ +#ifdef WIN32 +#include // Needed for `O_CREAT` and `O_RDWR` on Win32. + +#endif + #include "source/common/api/os_sys_calls_impl.h" +#include "test/test_common/environment.h" + #include "gtest/gtest.h" namespace Envoy { +// Test happy path for `open`, `pwrite`, `pread`, `fstat`, `close`, `stat` and `unlink`. +TEST(OsSyscallsTest, OpenPwritePreadFstatCloseStatUnlink) { + auto& os_syscalls = Api::OsSysCallsSingleton::get(); + std::string path{TestEnvironment::temporaryPath("envoy_test")}; + TestEnvironment::createPath(path); + std::string file_path = path + "/file"; + absl::string_view file_contents = "12345"; +// Test `open` +#ifdef WIN32 + int pmode = _S_IWRITE | _S_IREAD; +#else + int pmode = S_IRUSR | S_IWUSR; +#endif + Api::SysCallIntResult open_result = os_syscalls.open(file_path.c_str(), O_CREAT | O_RDWR, pmode); + EXPECT_NE(open_result.return_value_, -1); + EXPECT_EQ(open_result.errno_, 0); + os_fd_t fd = open_result.return_value_; +#ifdef WIN32 + // `pwrite` and `pread` are not supported. Just write some bytes so we can still test stat. + EXPECT_EQ(file_contents.size(), ::_write(fd, file_contents.begin(), file_contents.size())); +#else + // Test `pwrite` + Api::SysCallSizeResult write_result = + os_syscalls.pwrite(fd, file_contents.begin(), file_contents.size(), 0); + EXPECT_EQ(write_result.return_value_, file_contents.size()); + EXPECT_EQ(write_result.errno_, 0); + // Test `pread` + char read_buffer[5]; + Api::SysCallSizeResult read_result = os_syscalls.pread(fd, read_buffer, sizeof(read_buffer), 0); + EXPECT_EQ(read_result.return_value_, sizeof(read_buffer)); + EXPECT_EQ(read_result.errno_, 0); + absl::string_view read_buffer_view{read_buffer, sizeof(read_buffer)}; + EXPECT_EQ(file_contents, read_buffer_view); +#endif + // Test `fstat` + struct stat fstat_value; + Api::SysCallIntResult fstat_result = os_syscalls.fstat(fd, &fstat_value); + EXPECT_EQ(fstat_result.return_value_, 0); + EXPECT_EQ(fstat_result.errno_, 0); + EXPECT_EQ(fstat_value.st_size, file_contents.size()); + // Test `close` + Api::SysCallIntResult close_result = os_syscalls.close(fd); + EXPECT_EQ(close_result.return_value_, 0); + EXPECT_EQ(close_result.errno_, 0); + // Test `stat` + struct stat stat_value; + Api::SysCallIntResult stat_result = os_syscalls.stat(file_path.c_str(), &stat_value); + EXPECT_EQ(stat_result.return_value_, 0); + EXPECT_EQ(stat_result.errno_, 0); + EXPECT_EQ(stat_value.st_size, file_contents.size()); + // Test `unlink` + Api::SysCallIntResult unlink_result = os_syscalls.unlink(file_path.c_str()); + EXPECT_EQ(unlink_result.return_value_, 0); + EXPECT_EQ(unlink_result.errno_, 0); + TestEnvironment::removePath(path); +} + TEST(OsSyscallsTest, SetAlternateGetifaddrs) { auto& os_syscalls = Api::OsSysCallsSingleton::get(); const bool pre_alternate_support = os_syscalls.supportsGetifaddrs(); diff --git a/test/mocks/api/mocks.h b/test/mocks/api/mocks.h index 7d136a0784237..95f8bf8a8a179 100644 --- a/test/mocks/api/mocks.h +++ b/test/mocks/api/mocks.h @@ -94,6 +94,7 @@ class MockOsSysCalls : public OsSysCallsImpl { MOCK_METHOD(SysCallPtrResult, mmap, (void* addr, size_t length, int prot, int flags, int fd, off_t offset)); MOCK_METHOD(SysCallIntResult, stat, (const char* name, struct stat* stat)); + MOCK_METHOD(SysCallIntResult, fstat, (os_fd_t fd, struct stat* stat)); MOCK_METHOD(SysCallIntResult, chmod, (const std::string& name, mode_t mode)); MOCK_METHOD(int, setsockopt_, (os_fd_t sockfd, int level, int optname, const void* optval, socklen_t optlen));