diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index 72ce1e37320f..8c219b391aec 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn @@ -279,6 +279,8 @@ static_library("starboard_platform") { "android_media_session_client.cc", "application_android.cc", "application_android.h", + "asset_manager.cc", + "asset_manager.h", "atomic_public.h", "audio_decoder.cc", "audio_decoder.h", @@ -379,6 +381,7 @@ static_library("starboard_platform") { "player_set_max_video_input_size.h", "player_set_playback_rate.cc", "posix_emu/errno.cc", + "posix_emu/file.cc", "posix_emu/pthread.cc", "posix_emu/stat.cc", "sanitizer_options.cc", diff --git a/starboard/android/shared/asset_manager.cc b/starboard/android/shared/asset_manager.cc new file mode 100644 index 000000000000..a386544390db --- /dev/null +++ b/starboard/android/shared/asset_manager.cc @@ -0,0 +1,138 @@ +// 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/android/shared/asset_manager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "starboard/android/shared/file_internal.h" +#include "starboard/common/log.h" +#include "starboard/common/mutex.h" +#include "starboard/common/once.h" +#include "starboard/common/string.h" +#include "starboard/system.h" + +namespace starboard { +namespace android { +namespace shared { + +// static +SB_ONCE_INITIALIZE_FUNCTION(AssetManager, AssetManager::GetInstance); + +AssetManager::AssetManager() { + const int kPathSize = PATH_MAX / 2; + char path[kPathSize] = {0}; + SB_CHECK(SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) + << "Unable to get system temp path for AssetManager."; + SB_CHECK(starboard::strlcat(path, "/asset_tmp", kPathSize) < kPathSize) + << "Unable to construct temp path for AssetManager."; + tmp_root_ = path; + ClearTempDir(); +} + +uint64_t AssetManager::AcquireInternalFd() { + ScopedLock scoped_lock(mutex_); + do { + ++internal_fd_; + } while (in_use_internal_fd_set_.count(internal_fd_) == 1); + in_use_internal_fd_set_.insert(internal_fd_); + return internal_fd_; +} + +std::string AssetManager::TempFilepath(uint64_t internal_fd) const { + return tmp_root_ + "/" + std::to_string(internal_fd); +} + +int AssetManager::Open(const char* path) { + if (!path) { + return -1; + } + + AAsset* asset = OpenAndroidAsset(path); + if (!asset) { + SB_LOG(WARNING) << "Asset path not found within package: " << path; + return -1; + } + + // Create temporary POSIX file for the asset + uint64_t internal_fd = AcquireInternalFd(); + std::string filepath = TempFilepath(internal_fd); + int fd = open(filepath.c_str(), O_RDWR | O_TRUNC | O_CREAT); + if (fd < 0) { + mutex_.Acquire(); + in_use_internal_fd_set_.erase(internal_fd); + mutex_.Release(); + return -1; + } + + // Copy contents of asset into temporary file and then seek to start of file. + const off_t size = AAsset_getLength(asset); + const void* const data = AAsset_getBuffer(asset); + if (write(fd, data, size) != size || lseek(fd, 0, SEEK_SET) != 0) { + SB_LOG(WARNING) << "Failed to write temporary file for asset: " << path; + mutex_.Acquire(); + in_use_internal_fd_set_.erase(internal_fd); + mutex_.Release(); // Can't hold lock when calling close(); + close(fd); + return -1; + } + AAsset_close(asset); + + // Keep track of the internal fd so we can delete its file on close(); + mutex_.Acquire(); + fd_to_internal_fd_map_[fd] = internal_fd; + mutex_.Release(); + return fd; +} + +bool AssetManager::IsAssetFd(int fd) const { + ScopedLock scoped_lock(mutex_); + return fd_to_internal_fd_map_.count(fd) == 1; +} + +int AssetManager::Close(int fd) { + mutex_.Acquire(); + if (auto search = fd_to_internal_fd_map_.find(fd); + search != fd_to_internal_fd_map_.end()) { + uint64_t internal_fd = search->second; + fd_to_internal_fd_map_.erase(search); + in_use_internal_fd_set_.erase(internal_fd); + mutex_.Release(); // Can't hold lock when calling close(); + int retval = close(fd); + std::string filepath = TempFilepath(internal_fd); + if (unlink(filepath.c_str()) != 0) { + SB_LOG(WARNING) << "Failed to delete temporary file: " << filepath; + } + return retval; + } + mutex_.Release(); + return -1; +} + +void AssetManager::ClearTempDir() { + auto callback = [](const char* child, const struct stat*, int file_type, + struct FTW*) -> int { return remove(child); }; + nftw(tmp_root_.c_str(), callback, 32, FTW_DEPTH | FTW_PHYS); + mkdir(tmp_root_.c_str(), 0700); +} + +} // namespace shared +} // namespace android +} // namespace starboard diff --git a/starboard/android/shared/asset_manager.h b/starboard/android/shared/asset_manager.h new file mode 100644 index 000000000000..10f1a8b5d526 --- /dev/null +++ b/starboard/android/shared/asset_manager.h @@ -0,0 +1,54 @@ +// 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_ANDROID_SHARED_ASSET_MANAGER_H_ +#define STARBOARD_ANDROID_SHARED_ASSET_MANAGER_H_ + +#include +#include +#include + +#include "starboard/common/mutex.h" + +namespace starboard { +namespace android { +namespace shared { + +// This class handles opening/closing Android asset files as POSIX filehandles. +class AssetManager { + public: + static AssetManager* GetInstance(); + int Open(const char* path); + int Close(int fd); + bool IsAssetFd(int fd) const; + + private: + AssetManager(); + ~AssetManager() { ClearTempDir(); } + uint64_t AcquireInternalFd(); + std::string TempFilepath(uint64_t internal_fd) const; + void ClearTempDir(); + + std::string tmp_root_; + mutable Mutex mutex_; + uint64_t internal_fd_ = 0; // Guarded by |mutex_|. + std::set in_use_internal_fd_set_; // Guarded by |mutex_|. + std::map fd_to_internal_fd_map_; // Guarded by |mutex_|. +}; + +} // namespace shared +} // namespace android +} // namespace starboard + +#endif // STARBOARD_ANDROID_SHARED_ASSET_MANAGER_H_ diff --git a/starboard/android/shared/platform_configuration/BUILD.gn b/starboard/android/shared/platform_configuration/BUILD.gn index 0de86397e4f2..9c7bc708fb56 100644 --- a/starboard/android/shared/platform_configuration/BUILD.gn +++ b/starboard/android/shared/platform_configuration/BUILD.gn @@ -169,7 +169,11 @@ config("platform_configuration") { "-Wl,--wrap=eglSwapBuffers", ] if (!is_native_target_build) { - ldflags += [ "-Wl,--wrap=stat" ] + ldflags += [ + "-Wl,--wrap=close", + "-Wl,--wrap=open", + "-Wl,--wrap=stat", + ] } } diff --git a/starboard/android/shared/posix_emu/file.cc b/starboard/android/shared/posix_emu/file.cc new file mode 100644 index 000000000000..d18ebefb3997 --- /dev/null +++ b/starboard/android/shared/posix_emu/file.cc @@ -0,0 +1,61 @@ +// 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 + +#include + +#include "starboard/android/shared/asset_manager.h" +#include "starboard/android/shared/file_internal.h" +#include "starboard/common/log.h" +#include "starboard/configuration_constants.h" +#include "starboard/directory.h" +#include "starboard/log.h" + +using starboard::android::shared::AssetManager; +using starboard::android::shared::IsAndroidAssetPath; +using starboard::android::shared::OpenAndroidAsset; + +// /////////////////////////////////////////////////////////////////////////////// +// // Implementations below exposed externally in pure C for emulation. +// /////////////////////////////////////////////////////////////////////////////// + +extern "C" { +int __real_close(int fildes); +int __real_open(const char* path, int oflag, ...); + +int __wrap_close(int fildes) { + AssetManager* asset_manager = AssetManager::GetInstance(); + if (asset_manager->IsAssetFd(fildes)) { + return asset_manager->Close(fildes); + } + return __real_close(fildes); +} + +int __wrap_open(const char* path, int oflag, ...) { + if (!IsAndroidAssetPath(path)) { + va_list args; + va_start(args, oflag); + int fd; + if (oflag & O_CREAT) { + mode_t mode = va_arg(args, int); + return __real_open(path, oflag, mode); + } else { + return __real_open(path, oflag); + } + } + return AssetManager::GetInstance()->Open(path); +} + +} // extern "C" diff --git a/starboard/android/shared/test_filters.py b/starboard/android/shared/test_filters.py index 3d30f3deac2e..7c90f5484614 100644 --- a/starboard/android/shared/test_filters.py +++ b/starboard/android/shared/test_filters.py @@ -65,12 +65,6 @@ 'PosixDirectoryOpenTest.SunnyDayStaticContent', 'PosixFileGetPathInfoTest.WorksOnStaticContentDirectories', - # These POSIX tests should be disabled until asset manager starboard - # extension is implemented. - 'PosixFileGetInfoTest.WorksOnStaticContentFiles', - 'PosixFileReadTest/*.ReadStaticContent', - 'PosixFileSeekTest.FromEndInStaticContentWorks', - # These tests are disabled due to not receiving the kEndOfStream # player state update within the specified timeout. 'SbPlayerGetAudioConfigurationTests/SbPlayerGetAudioConfigurationTest.NoInput/*',