diff --git a/starboard/CHANGELOG.md b/starboard/CHANGELOG.md index f8469861b3a9..7e76464da55c 100644 --- a/starboard/CHANGELOG.md +++ b/starboard/CHANGELOG.md @@ -9,6 +9,10 @@ since the version previous to it. ## Version 16 +### Added standard POSIX file open and close APIs. +The standard API `open` can be used from `fcntl.h` and `close` can be used from +. + ### Added standard POSIX socket bind/listen/connect/accept APIs. The standard API `bind`, `listen`, `connect`, `accept` can be used from and `getifaddrs`, `freeifaddrs` can be used from . diff --git a/starboard/elf_loader/exported_symbols.cc b/starboard/elf_loader/exported_symbols.cc index bbc98c6c795a..6d4443810130 100644 --- a/starboard/elf_loader/exported_symbols.cc +++ b/starboard/elf_loader/exported_symbols.cc @@ -14,6 +14,7 @@ #include "starboard/elf_loader/exported_symbols.h" +#include #include #include #include @@ -428,6 +429,7 @@ ExportedSymbols::ExportedSymbols() { REGISTER_SYMBOL(mprotect); REGISTER_SYMBOL(msync); REGISTER_SYMBOL(munmap); + REGISTER_SYMBOL(open); REGISTER_SYMBOL(posix_memalign); REGISTER_SYMBOL(realloc); REGISTER_SYMBOL(setsockopt); diff --git a/starboard/nplb/BUILD.gn b/starboard/nplb/BUILD.gn index 853c644334a4..1a971fc06329 100644 --- a/starboard/nplb/BUILD.gn +++ b/starboard/nplb/BUILD.gn @@ -135,6 +135,8 @@ target(gtest_target_type, "nplb") { "player_test_util.h", "player_write_sample_test.cc", "posix_compliance/posix_arpa_inet_test.cc", + "posix_compliance/posix_file_close_test.cc", + "posix_compliance/posix_file_open_test.cc", "posix_compliance/posix_memory_map_test.cc", "posix_compliance/posix_mutex_acquire_test.cc", "posix_compliance/posix_mutex_acquire_try_test.cc", diff --git a/starboard/nplb/posix_compliance/posix_file_close_test.cc b/starboard/nplb/posix_compliance/posix_file_close_test.cc new file mode 100644 index 000000000000..7b79b2b00169 --- /dev/null +++ b/starboard/nplb/posix_compliance/posix_file_close_test.cc @@ -0,0 +1,36 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// 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. + +#if SB_API_VERSION >= 16 + +// close is partially tested in posix_file_open_test.cc. + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { +namespace { + +TEST(PosixFileCloseTest, CloseInvalidFails) { + int fd = -1; + EXPECT_FALSE(close(fd) == 0); +} + +} // namespace +} // namespace nplb +} // namespace starboard + +#endif // SB_API_VERSION >= 16 diff --git a/starboard/nplb/posix_compliance/posix_file_helpers.cc b/starboard/nplb/posix_compliance/posix_file_helpers.cc new file mode 100644 index 000000000000..55941deb9209 --- /dev/null +++ b/starboard/nplb/posix_compliance/posix_file_helpers.cc @@ -0,0 +1,69 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// 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 "starboard/nplb/posix_compliance/posix_file_helpers.h" + +#include +#include + +#include +#include +#include + +#include "starboard/configuration_constants.h" +#include "starboard/file.h" +#include "starboard/shared/posix/file_internal.h" +#include "starboard/system.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +// static +std::string ScopedRandomFile::MakeRandomFilePath() { + std::ostringstream filename_stream; + filename_stream << GetTempDir(); + if (!filename_stream.tellp()) { + return ""; + } + + filename_stream << kSbFileSepChar << MakeRandomFilename(); + return filename_stream.str(); +} + +std::string ScopedRandomFile::MakeRandomFile(int length) { + std::string filename = MakeRandomFilePath(); + if (filename.empty()) { + return filename; + } + + int file = open(filename.c_str(), O_CREAT | O_WRONLY); + EXPECT_TRUE(fcntl(file, F_GETFD)); + if (!fcntl(file, F_GETFD)) { + return ""; + } + + char* data = new char[length]; + for (int i = 0; i < length; ++i) { + data[i] = static_cast(i & 0xFF); + } + + bool result = close(file); + EXPECT_TRUE(result) << "Failed to close " << filename; + delete[] data; + return filename; +} + +} // namespace nplb +} // namespace starboard diff --git a/starboard/nplb/posix_compliance/posix_file_helpers.h b/starboard/nplb/posix_compliance/posix_file_helpers.h new file mode 100644 index 000000000000..758594f3bc6d --- /dev/null +++ b/starboard/nplb/posix_compliance/posix_file_helpers.h @@ -0,0 +1,101 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// 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 STARBOARD_NPLB_POSIX_COMPLIANCE_POSIX_FILE_HELPERS_H_ +#define STARBOARD_NPLB_POSIX_COMPLIANCE_POSIX_FILE_HELPERS_H_ + +#include +#include + +#include "starboard/file.h" + +namespace starboard { +namespace nplb { + +// Gets the temporary directory in which ScopedRandomFile places its files. +std::string GetTempDir(); + +// Creates a random file of the given length, and deletes it when the instance +// falls out of scope. +class ScopedRandomFile { + public: + enum { + kDefaultLength = 64, + }; + + enum Create { + kCreate, + kDontCreate, + }; + + // Will create a file of |kDefaultLength| bytes long. + ScopedRandomFile() : size_(kDefaultLength) { + filename_ = MakeRandomFile(size_); + } + + // Will create a file |length| bytes long. + explicit ScopedRandomFile(int length) : size_(length) { + filename_ = MakeRandomFile(size_); + } + + // Will either create a file |length| bytes long, or will just generate a + // filename. |create| is whether to create the file or not. + ScopedRandomFile(int length, Create create) : size_(length) { + filename_ = + (create == kCreate ? MakeRandomFile(size_) : MakeRandomFilePath()); + } + + // Will either create a file of |kDefaultLength| bytes long, or will just + // generate a filename. |create| is whether to create the file or not. + explicit ScopedRandomFile(Create create) : size_(kDefaultLength) { + filename_ = + (create == kCreate ? MakeRandomFile(size_) : MakeRandomFilePath()); + } + + ~ScopedRandomFile() { SbFileDelete(filename_.c_str()); } + + // Creates and returns a random filename (no path), but does not create the + // file. + static std::string MakeRandomFilename(); + + // Returns the filename generated for this file. + const std::string& filename() const { return filename_; } + + // Returns the SPECIFIED size of the file (not the size returned by the + // filesystem). + const int size() const { return size_; } + + // Checks |buffer| of size |size| against this class's write pattern, offset + // by |pattern_offset|. Failures print the original line number |line|. + static void ExpectPattern(int pattern_offset, + void* buffer, + int size, + int line); + + private: + // Creates a file with a random name and |length| bytes, returning the path to + // the new file. + static std::string MakeRandomFile(int length); + + // Creates and returns a path to a random file, but does not create the file. + static std::string MakeRandomFilePath(); + + std::string filename_; + int size_; +}; + +} // namespace nplb +} // namespace starboard + +#endif // STARBOARD_NPLB_POSIX_COMPLIANCE_POSIX_FILE_HELPERS_H_ diff --git a/starboard/nplb/posix_compliance/posix_file_open_test.cc b/starboard/nplb/posix_compliance/posix_file_open_test.cc new file mode 100644 index 000000000000..52ba9078d2f5 --- /dev/null +++ b/starboard/nplb/posix_compliance/posix_file_open_test.cc @@ -0,0 +1,135 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// 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. + +#if SB_API_VERSION >= 16 + +#include +#include +#include + +#include "starboard/file.h" +#include "starboard/nplb/posix_compliance/posix_file_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { +namespace { + +void BasicTest(bool existing, + int open_flags, + bool expected_created, + bool expected_success, + int original_line, + mode_t mode = S_IRUSR | S_IWUSR) { + ScopedRandomFile random_file(existing ? ScopedRandomFile::kCreate + : ScopedRandomFile::kDontCreate); + const std::string& filename = random_file.filename(); +#define SB_FILE_OPEN_TEST_CONTEXT \ + "existing=" << existing << ", flags=0x" << std::hex << open_flags \ + << std::dec << ", expected_created=" << expected_created \ + << ", expected_success=" << expected_success \ + << ", filename=" << filename \ + << ", original_line=" << original_line + + if (!existing) { + EXPECT_FALSE(SbFileExists(filename.c_str())) << SB_FILE_OPEN_TEST_CONTEXT; + if (SbFileExists(filename.c_str())) { + return; + } + } + + bool created = !expected_created; + int fd; +#ifdef _WIN32 + // File mode is set along with O_CREAT flag. + // Windows only supports 1)_S_IREAD, which is mapped to S_IRUSR, 2) _S_IWRITE, + // which is mapped to S_IWUSR, and 3) _S_IREAD | _S_IWRITE. + if (open_flags & O_CREAT && (open_flags == S_IRUSR || open_flags == S_IWUSR || + open_flags == (S_IRUSR | S_IWUSR))) { + fd = open(filename.c_str(), open_flags, mode); + } else { + fd = open(filename.c_str(), open_flags); + } +#else + fd = (open_flags & O_CREAT) ? open(filename.c_str(), open_flags, mode) + : open(filename.c_str(), open_flags); +#endif + + if (!expected_success) { + EXPECT_FALSE(fd >= 0) << SB_FILE_OPEN_TEST_CONTEXT; + + // Try to clean up in case test fails. + if (!(fd < 0)) { + close(fd); + } + } else { + EXPECT_TRUE(fd >= 0); + if (fd >= 0) { + int result = close(fd); + EXPECT_TRUE(result == 0) << SB_FILE_OPEN_TEST_CONTEXT; + } + } +#undef SB_FILE_OPEN_TEST_CONTEXT +} + +TEST(PosixFileOpenTest, OpenOnlyOpensExistingFile) { + BasicTest(true, O_RDONLY, false, true, __LINE__); +} + +TEST(PosixFileOpenTest, OpenOnlyDoesNotOpenNonExistingFile) { + BasicTest(false, O_RDONLY, false, false, __LINE__); +} + +TEST(PosixFileOpenTest, CreateOnlyDoesNotCreateExistingFile) { + BasicTest(true, O_CREAT | O_EXCL | O_WRONLY, false, false, __LINE__); +} + +TEST(PosixFileOpenTest, CreateOnlyCreatesNonExistingFile) { + BasicTest(false, O_CREAT | O_EXCL | O_WRONLY, true, true, __LINE__); +} + +TEST(PosixFileOpenTest, OpenAlwaysOpensExistingFile) { + BasicTest(true, O_CREAT | O_WRONLY, false, true, __LINE__); +} + +TEST(PosixFileOpenTest, OpenAlwaysCreatesNonExistingFile) { + BasicTest(false, O_CREAT | O_WRONLY, true, true, __LINE__); +} + +TEST(PosixFileOpenTest, OpenAlwaysWithLinuxSpecificMode) { + BasicTest(false, O_CREAT | O_TRUNC | O_WRONLY, true, true, __LINE__, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +} + +TEST(PosixFileOpenTest, CreateAlwaysTruncatesExistingFile) { + BasicTest(true, O_CREAT | O_TRUNC | O_WRONLY, true, true, __LINE__); +} + +TEST(PosixFileOpenTest, CreateAlwaysCreatesNonExistingFile) { + BasicTest(false, O_CREAT | O_TRUNC | O_WRONLY, true, true, __LINE__); +} + +TEST(PosixFileOpenTest, OpenTruncatedTruncatesExistingFile) { + BasicTest(true, O_TRUNC | O_WRONLY, false, true, __LINE__); +} + +TEST(PosixFileOpenTest, OpenTruncatedDoesNotCreateNonExistingFile) { + BasicTest(false, O_TRUNC | O_WRONLY, false, false, __LINE__); +} + +} // namespace +} // namespace nplb +} // namespace starboard + +#endif // SB_API_VERSION >= 16 diff --git a/starboard/shared/win32/posix_emu/include/fcntl.h b/starboard/shared/win32/posix_emu/include/fcntl.h index 7fd804cd0c56..64a7b222bd38 100644 --- a/starboard/shared/win32/posix_emu/include/fcntl.h +++ b/starboard/shared/win32/posix_emu/include/fcntl.h @@ -17,7 +17,34 @@ #include <../ucrt/fcntl.h> // The Visual Studio version of this same file #include // Needed for `open`, which is in fcntl.h on POSIX +#include +#undef open #undef close // in unistd.h on POSIX, and handles both files and sockets +typedef int mode_t; + +// For the POSIX modes that do not have a Windows equivalent, the modes +// defined here use the POSIX values left shifted 16 bits. +// Passing these into Windows file I/O functions has no effect. + +static const mode_t S_ISUID = 0x40000000; +static const mode_t S_ISGID = 0x20000000; +static const mode_t S_ISVTX = 0x10000000; +static const mode_t S_IRWXU = 0x07000000; +static const mode_t S_IRUSR = _S_IREAD; // read by user +static const mode_t S_IWUSR = _S_IWRITE; // write by user +static const mode_t S_IXUSR = 0x01000000; +static const mode_t S_IRGRP = 0x00400000; +static const mode_t S_IWGRP = 0x00200000; +static const mode_t S_IXGRP = 0x00100000; +static const mode_t S_IRWXO = 0x00070000; +static const mode_t S_IROTH = 0x00040000; +static const mode_t S_IWOTH = 0x00020000; +static const mode_t S_IXOTH = 0x00010000; + +static const mode_t MS_MODE_MASK = 0x0000ffff; + +int open(const char* path, int oflag, ...); + #endif // STARBOARD_SHARED_WIN32_POSIX_EMU_INCLUDE_FCNTL_H_ diff --git a/starboard/shared/win32/posix_emu/socket.cc b/starboard/shared/win32/posix_emu/socket.cc index 7be300ca1d85..eab31cf143a4 100644 --- a/starboard/shared/win32/posix_emu/socket.cc +++ b/starboard/shared/win32/posix_emu/socket.cc @@ -13,6 +13,8 @@ // limitations under the License. // We specifically do not include since the define causes a loop + +#include #include // Needed for file-specific `_close`. #include // Our version that declares generic `close`. #include @@ -30,18 +32,24 @@ static int gen_fd() { return fd; } +struct FileOrSocket { + bool is_file; + int file; + SOCKET socket; +}; + struct CriticalSection { CriticalSection() { InitializeCriticalSection(&critical_section_); } CRITICAL_SECTION critical_section_; }; -static std::map* g_map_addr = nullptr; +static std::map* g_map_addr = nullptr; static CriticalSection g_critical_section; -static int handle_db_put(SOCKET socket_handle) { +int handle_db_put(FileOrSocket handle) { EnterCriticalSection(&g_critical_section.critical_section_); if (g_map_addr == nullptr) { - g_map_addr = new std::map(); + g_map_addr = new std::map(); } int fd = gen_fd(); @@ -50,33 +58,34 @@ static int handle_db_put(SOCKET socket_handle) { while (g_map_addr->find(fd) != g_map_addr->end()) { fd = gen_fd(); } + g_map_addr->insert({fd, handle}); - g_map_addr->insert({fd, socket_handle}); LeaveCriticalSection(&g_critical_section.critical_section_); return fd; } -static SOCKET handle_db_get(int fd, bool erase) { +static FileOrSocket handle_db_get(int fd, bool erase) { + FileOrSocket invalid_handle = {/*is_file=*/false, -1, INVALID_SOCKET}; if (fd < 0) { - return INVALID_SOCKET; + return invalid_handle; } EnterCriticalSection(&g_critical_section.critical_section_); if (g_map_addr == nullptr) { - g_map_addr = new std::map(); - return INVALID_SOCKET; + g_map_addr = new std::map(); + return invalid_handle; } auto itr = g_map_addr->find(fd); if (itr == g_map_addr->end()) { - return INVALID_SOCKET; + return invalid_handle; } - SOCKET socket_handle = itr->second; + FileOrSocket handle = itr->second; if (erase) { g_map_addr->erase(fd); } LeaveCriticalSection(&g_critical_section.critical_section_); - return socket_handle; + return handle; } /////////////////////////////////////////////////////////////////////////////// @@ -94,22 +103,48 @@ int sb_socket(int domain, int type, int protocol) { return -1; } - return handle_db_put(socket_handle); + FileOrSocket handle = {/*is_file=*/false, -1, socket_handle}; + + return handle_db_put(handle); +} + +int open(const char* path, int oflag, ...) { + va_list args; + va_start(args, oflag); + int fd; + mode_t mode; + if (oflag & O_CREAT) { + mode = va_arg(args, mode_t); + fd = _open(path, oflag, mode & MS_MODE_MASK); + } else { + fd = _open(path, oflag); + } + va_end(args); + + if (fd < 0) { + return fd; + } + + FileOrSocket handle = {/*is_file=*/true, fd, INVALID_SOCKET}; + return handle_db_put(handle); } int close(int fd) { - SOCKET socket_handle = handle_db_get(fd, true); + FileOrSocket handle = handle_db_get(fd, true); - if (socket_handle != INVALID_SOCKET) { - return closesocket(socket_handle); + if (!handle.is_file && handle.socket == INVALID_SOCKET) { + // TODO: update errno with file operation error + return -1; + } else if (!handle.is_file) { + return closesocket(handle.socket); } // This is then a file handle, so use Windows `_close` API. - return _close(fd); + return _close(handle.file); } int sb_bind(int socket, const struct sockaddr* address, socklen_t address_len) { - SOCKET socket_handle = handle_db_get(socket, false); + SOCKET socket_handle = handle_db_get(socket, false).socket; if (socket_handle == INVALID_SOCKET) { // TODO: update errno with file operation error return -1; @@ -119,7 +154,7 @@ int sb_bind(int socket, const struct sockaddr* address, socklen_t address_len) { } int sb_listen(int socket, int backlog) { - SOCKET socket_handle = handle_db_get(socket, false); + SOCKET socket_handle = handle_db_get(socket, false).socket; if (socket_handle == INVALID_SOCKET) { // TODO: update errno with file operation error return -1; @@ -129,7 +164,7 @@ int sb_listen(int socket, int backlog) { } int sb_accept(int socket, sockaddr* addr, int* addrlen) { - SOCKET socket_handle = handle_db_get(socket, false); + SOCKET socket_handle = handle_db_get(socket, false).socket; if (socket_handle == INVALID_SOCKET) { // TODO: update errno with file operation error return -1; @@ -140,11 +175,13 @@ int sb_accept(int socket, sockaddr* addr, int* addrlen) { // TODO: update errno with file operation error return -1; } - return handle_db_put(accept_handle); + + FileOrSocket handle = {/*is_file=*/false, -1, accept_handle}; + return handle_db_put(handle); } int sb_connect(int socket, sockaddr* name, int namelen) { - SOCKET socket_handle = handle_db_get(socket, false); + SOCKET socket_handle = handle_db_get(socket, false).socket; if (socket_handle == INVALID_SOCKET) { // TODO: update errno with file operation error return -1; @@ -158,14 +195,14 @@ int sb_setsockopt(int socket, int option_name, const void* option_value, int option_len) { - SOCKET socket_handle = handle_db_get(socket, false); + FileOrSocket handle = handle_db_get(socket, false); - if (socket_handle == INVALID_SOCKET) { + if (handle.is_file || handle.socket == INVALID_SOCKET) { return -1; } int result = - setsockopt(socket_handle, level, option_name, + setsockopt(handle.socket, level, option_name, reinterpret_cast(option_value), option_len); // TODO(b/321999529): Windows returns SOCKET_ERROR on failure. The specific // error code can be retrieved by calling WSAGetLastError(), and Posix returns