| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * 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 "artd.h" |
| |
| #include <fcntl.h> |
| #include <sys/inotify.h> |
| #include <sys/mount.h> |
| #include <sys/poll.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <climits> |
| #include <csignal> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <cstring> |
| #include <filesystem> |
| #include <functional> |
| #include <iterator> |
| #include <map> |
| #include <memory> |
| #include <mutex> |
| #include <new> |
| #include <optional> |
| #include <ostream> |
| #include <regex> |
| #include <string> |
| #include <string_view> |
| #include <system_error> |
| #include <type_traits> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include "aidl/com/android/server/art/ArtConstants.h" |
| #include "aidl/com/android/server/art/BnArtd.h" |
| #include "aidl/com/android/server/art/DexoptTrigger.h" |
| #include "aidl/com/android/server/art/IArtdCancellationSignal.h" |
| #include "aidl/com/android/server/art/IArtdNotification.h" |
| #include "android-base/errors.h" |
| #include "android-base/file.h" |
| #include "android-base/logging.h" |
| #include "android-base/parseint.h" |
| #include "android-base/result.h" |
| #include "android-base/scopeguard.h" |
| #include "android-base/strings.h" |
| #include "android-base/unique_fd.h" |
| #include "android/binder_auto_utils.h" |
| #include "android/binder_interface_utils.h" |
| #include "android/binder_manager.h" |
| #include "android/binder_process.h" |
| #include "base/compiler_filter.h" |
| #include "base/file_magic.h" |
| #include "base/file_utils.h" |
| #include "base/globals.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/mem_map.h" |
| #include "base/memfd.h" |
| #include "base/os.h" |
| #include "base/pidfd.h" |
| #include "base/time_utils.h" |
| #include "base/zip_archive.h" |
| #include "cmdline_types.h" |
| #include "dex/dex_file_loader.h" |
| #include "exec_utils.h" |
| #include "file_utils.h" |
| #include "fstab/fstab.h" |
| #include "oat/oat_file_assistant.h" |
| #include "oat/oat_file_assistant_context.h" |
| #include "odrefresh/odrefresh.h" |
| #include "path_utils.h" |
| #include "profman/profman_result.h" |
| #include "selinux/android.h" |
| #include "service.h" |
| #include "tools/binder_utils.h" |
| #include "tools/cmdline_builder.h" |
| #include "tools/tools.h" |
| |
| namespace art { |
| namespace artd { |
| |
| namespace { |
| |
| using ::aidl::com::android::server::art::ArtConstants; |
| using ::aidl::com::android::server::art::ArtdDexoptResult; |
| using ::aidl::com::android::server::art::ArtifactsLocation; |
| using ::aidl::com::android::server::art::ArtifactsPath; |
| using ::aidl::com::android::server::art::CopyAndRewriteProfileResult; |
| using ::aidl::com::android::server::art::DexMetadataPath; |
| using ::aidl::com::android::server::art::DexoptOptions; |
| using ::aidl::com::android::server::art::DexoptTrigger; |
| using ::aidl::com::android::server::art::FileVisibility; |
| using ::aidl::com::android::server::art::FsPermission; |
| using ::aidl::com::android::server::art::GetDexoptNeededResult; |
| using ::aidl::com::android::server::art::GetDexoptStatusResult; |
| using ::aidl::com::android::server::art::IArtdCancellationSignal; |
| using ::aidl::com::android::server::art::IArtdNotification; |
| using ::aidl::com::android::server::art::MergeProfileOptions; |
| using ::aidl::com::android::server::art::OutputArtifacts; |
| using ::aidl::com::android::server::art::OutputProfile; |
| using ::aidl::com::android::server::art::PriorityClass; |
| using ::aidl::com::android::server::art::ProfilePath; |
| using ::aidl::com::android::server::art::RuntimeArtifactsPath; |
| using ::aidl::com::android::server::art::VdexPath; |
| using ::android::base::Basename; |
| using ::android::base::Dirname; |
| using ::android::base::ErrnoError; |
| using ::android::base::Error; |
| using ::android::base::Join; |
| using ::android::base::make_scope_guard; |
| using ::android::base::ParseInt; |
| using ::android::base::ReadFileToString; |
| using ::android::base::Result; |
| using ::android::base::Split; |
| using ::android::base::Tokenize; |
| using ::android::base::Trim; |
| using ::android::base::unique_fd; |
| using ::android::base::WriteStringToFd; |
| using ::android::base::WriteStringToFile; |
| using ::android::fs_mgr::FstabEntry; |
| using ::art::service::ValidateClassLoaderContext; |
| using ::art::service::ValidateDexPath; |
| using ::art::tools::CmdlineBuilder; |
| using ::art::tools::Fatal; |
| using ::art::tools::GetProcMountsAncestorsOfPath; |
| using ::art::tools::NonFatal; |
| using ::ndk::ScopedAStatus; |
| |
| using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; |
| using TmpProfilePath = ProfilePath::TmpProfilePath; |
| using WritableProfilePath = ProfilePath::WritableProfilePath; |
| |
| constexpr const char* kServiceName = "artd"; |
| constexpr const char* kPreRebootServiceName = "artd_pre_reboot"; |
| constexpr const char* kArtdCancellationSignalType = "ArtdCancellationSignal"; |
| constexpr const char* kDefaultPreRebootTmpDir = "/mnt/artd_tmp"; |
| |
| // Timeout for short operations, such as merging profiles. |
| constexpr int kShortTimeoutSec = 60; // 1 minute. |
| |
| // Timeout for long operations, such as compilation. We set it to be smaller than the Package |
| // Manager watchdog (PackageManagerService.WATCHDOG_TIMEOUT, 10 minutes), so that if the operation |
| // is called from the Package Manager's thread handler, it will be aborted before that watchdog |
| // would take down the system server. |
| constexpr int kLongTimeoutSec = 570; // 9.5 minutes. |
| |
| std::optional<int64_t> GetSize(std::string_view path) { |
| std::error_code ec; |
| int64_t size = std::filesystem::file_size(path, ec); |
| if (ec) { |
| // It is okay if the file does not exist. We don't have to log it. |
| if (ec.value() != ENOENT) { |
| LOG(ERROR) << ART_FORMAT("Failed to get the file size of '{}': {}", path, ec.message()); |
| } |
| return std::nullopt; |
| } |
| return size; |
| } |
| |
| bool DeleteFile(const std::string& path) { |
| std::error_code ec; |
| std::filesystem::remove(path, ec); |
| if (ec) { |
| LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", path, ec.message()); |
| return false; |
| } |
| return true; |
| } |
| |
| // Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an |
| // error occurs. |
| int64_t GetSizeAndDeleteFile(const std::string& path) { |
| std::optional<int64_t> size = GetSize(path); |
| if (!size.has_value()) { |
| return 0; |
| } |
| if (!DeleteFile(path)) { |
| return 0; |
| } |
| return size.value(); |
| } |
| |
| Result<CompilerFilter::Filter> ParseCompilerFilter(const std::string& compiler_filter_str) { |
| CompilerFilter::Filter compiler_filter; |
| if (!CompilerFilter::ParseCompilerFilter(compiler_filter_str.c_str(), &compiler_filter)) { |
| return Errorf("Failed to parse compiler filter '{}'", compiler_filter_str); |
| } |
| return compiler_filter; |
| } |
| |
| OatFileAssistant::DexOptTrigger DexOptTriggerFromAidl(int32_t aidl_value) { |
| OatFileAssistant::DexOptTrigger trigger{}; |
| if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_BETTER)) != 0) { |
| trigger.targetFilterIsBetter = true; |
| } |
| if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_SAME)) != 0) { |
| trigger.targetFilterIsSame = true; |
| } |
| if ((aidl_value & static_cast<int32_t>(DexoptTrigger::COMPILER_FILTER_IS_WORSE)) != 0) { |
| trigger.targetFilterIsWorse = true; |
| } |
| if ((aidl_value & static_cast<int32_t>(DexoptTrigger::PRIMARY_BOOT_IMAGE_BECOMES_USABLE)) != 0) { |
| trigger.primaryBootImageBecomesUsable = true; |
| } |
| if ((aidl_value & static_cast<int32_t>(DexoptTrigger::NEED_EXTRACTION)) != 0) { |
| trigger.needExtraction = true; |
| } |
| return trigger; |
| } |
| |
| ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) { |
| switch (location) { |
| case OatFileAssistant::Location::kLocationNoneOrError: |
| return ArtifactsLocation::NONE_OR_ERROR; |
| case OatFileAssistant::Location::kLocationOat: |
| return ArtifactsLocation::DALVIK_CACHE; |
| case OatFileAssistant::Location::kLocationOdex: |
| return ArtifactsLocation::NEXT_TO_DEX; |
| case OatFileAssistant::Location::kLocationDm: |
| return ArtifactsLocation::DM; |
| // No default. All cases should be explicitly handled, or the compilation will fail. |
| } |
| // This should never happen. Just in case we get a non-enumerator value. |
| LOG(FATAL) << "Unexpected Location " << location; |
| } |
| |
| Result<bool> CreateDir(const std::string& path) { |
| std::error_code ec; |
| bool created = std::filesystem::create_directory(path, ec); |
| if (ec) { |
| return Errorf("Failed to create directory '{}': {}", path, ec.message()); |
| } |
| return created; |
| } |
| |
| Result<void> PrepareArtifactsDir(const std::string& path, const FsPermission& fs_permission) { |
| bool created = OR_RETURN(CreateDir(path)); |
| |
| auto cleanup = make_scope_guard([&] { |
| if (created) { |
| std::error_code ec; |
| std::filesystem::remove(path, ec); |
| } |
| }); |
| |
| if (chmod(path.c_str(), DirFsPermissionToMode(fs_permission)) != 0) { |
| return ErrnoErrorf("Failed to chmod directory '{}'", path); |
| } |
| OR_RETURN(Chown(path, fs_permission)); |
| |
| cleanup.Disable(); |
| return {}; |
| } |
| |
| Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts, |
| /*out*/ std::string* oat_dir_path) { |
| if (output_artifacts.artifactsPath.isInDalvikCache) { |
| return {}; |
| } |
| |
| std::filesystem::path oat_path( |
| OR_RETURN(BuildArtifactsPath(output_artifacts.artifactsPath)).oat_path); |
| std::filesystem::path isa_dir = oat_path.parent_path(); |
| std::filesystem::path oat_dir = isa_dir.parent_path(); |
| DCHECK_EQ(oat_dir.filename(), "oat"); |
| |
| OR_RETURN(PrepareArtifactsDir(oat_dir, output_artifacts.permissionSettings.dirFsPermission)); |
| OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission)); |
| *oat_dir_path = oat_dir; |
| return {}; |
| } |
| |
| Result<FileVisibility> GetFileVisibility(const std::string& file) { |
| std::error_code ec; |
| std::filesystem::file_status status = std::filesystem::status(file, ec); |
| if (!std::filesystem::status_known(status)) { |
| return Errorf("Failed to get status of '{}': {}", file, ec.message()); |
| } |
| if (!std::filesystem::exists(status)) { |
| return FileVisibility::NOT_FOUND; |
| } |
| |
| return (status.permissions() & std::filesystem::perms::others_read) != |
| std::filesystem::perms::none ? |
| FileVisibility::OTHER_READABLE : |
| FileVisibility::NOT_OTHER_READABLE; |
| } |
| |
| Result<ArtdCancellationSignal*> ToArtdCancellationSignal(IArtdCancellationSignal* input) { |
| if (input == nullptr) { |
| return Error() << "Cancellation signal must not be nullptr"; |
| } |
| // We cannot use `dynamic_cast` because ART code is compiled with `-fno-rtti`, so we have to check |
| // the magic number. |
| int64_t type; |
| if (!input->getType(&type).isOk() || |
| type != reinterpret_cast<intptr_t>(kArtdCancellationSignalType)) { |
| // The cancellation signal must be created by `Artd::createCancellationSignal`. |
| return Error() << "Invalid cancellation signal type"; |
| } |
| return static_cast<ArtdCancellationSignal*>(input); |
| } |
| |
| Result<void> CopyFile(const std::string& src_path, const NewFile& dst_file) { |
| std::string content; |
| if (!ReadFileToString(src_path, &content)) { |
| return Errorf("Failed to read file '{}': {}", src_path, strerror(errno)); |
| } |
| if (!WriteStringToFd(content, dst_file.Fd())) { |
| return Errorf("Failed to write file '{}': {}", dst_file.TempPath(), strerror(errno)); |
| } |
| if (fsync(dst_file.Fd()) != 0) { |
| return Errorf("Failed to flush file '{}': {}", dst_file.TempPath(), strerror(errno)); |
| } |
| if (lseek(dst_file.Fd(), /*offset=*/0, SEEK_SET) != 0) { |
| return Errorf( |
| "Failed to reset the offset for file '{}': {}", dst_file.TempPath(), strerror(errno)); |
| } |
| return {}; |
| } |
| |
| Result<void> SetLogVerbosity() { |
| std::string options = |
| android::base::GetProperty("dalvik.vm.artd-verbose", /*default_value=*/"oat"); |
| if (options.empty()) { |
| return {}; |
| } |
| |
| CmdlineType<LogVerbosity> parser; |
| CmdlineParseResult<LogVerbosity> result = parser.Parse(options); |
| if (!result.IsSuccess()) { |
| return Error() << result.GetMessage(); |
| } |
| |
| gLogVerbosity = result.ReleaseValue(); |
| return {}; |
| } |
| |
| CopyAndRewriteProfileResult AnalyzeCopyAndRewriteProfileFailure( |
| File* src, ProfmanResult::CopyAndUpdateResult result) { |
| DCHECK(result == ProfmanResult::kCopyAndUpdateNoMatch || |
| result == ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile); |
| |
| auto bad_profile = [&](std::string_view error_msg) { |
| return CopyAndRewriteProfileResult{ |
| .status = CopyAndRewriteProfileResult::Status::BAD_PROFILE, |
| .errorMsg = ART_FORMAT("Failed to load profile '{}': {}", src->GetPath(), error_msg)}; |
| }; |
| CopyAndRewriteProfileResult no_profile{.status = CopyAndRewriteProfileResult::Status::NO_PROFILE, |
| .errorMsg = ""}; |
| |
| int64_t length = src->GetLength(); |
| if (length < 0) { |
| return bad_profile(strerror(-length)); |
| } |
| if (length == 0) { |
| return no_profile; |
| } |
| |
| std::string error_msg; |
| uint32_t magic; |
| if (!ReadMagicAndReset(src->Fd(), &magic, &error_msg)) { |
| return bad_profile(error_msg); |
| } |
| if (IsZipMagic(magic)) { |
| std::unique_ptr<ZipArchive> zip_archive( |
| ZipArchive::OpenFromOwnedFd(src->Fd(), src->GetPath().c_str(), &error_msg)); |
| if (zip_archive == nullptr) { |
| return bad_profile(error_msg); |
| } |
| std::unique_ptr<ZipEntry> zip_entry( |
| zip_archive->Find(ArtConstants::DEX_METADATA_PROFILE_ENTRY, &error_msg)); |
| if (zip_entry == nullptr || zip_entry->GetUncompressedLength() == 0) { |
| return no_profile; |
| } |
| } |
| |
| if (result == ProfmanResult::kCopyAndUpdateNoMatch) { |
| return bad_profile( |
| "The profile does not match the APK (The checksums in the profile do not match the " |
| "checksums of the .dex files in the APK)"); |
| } |
| return bad_profile("The profile is in the wrong format or an I/O error has occurred"); |
| } |
| |
| // Returns the fd on success, or an invalid fd if the dex file contains no profile, or error if any |
| // error occurs. |
| Result<File> ExtractEmbeddedProfileToFd(const std::string& dex_path) { |
| std::unique_ptr<File> dex_file = OR_RETURN(OpenFileForReading(dex_path)); |
| |
| std::string error_msg; |
| uint32_t magic; |
| if (!ReadMagicAndReset(dex_file->Fd(), &magic, &error_msg)) { |
| return Error() << error_msg; |
| } |
| if (!IsZipMagic(magic)) { |
| if (DexFileLoader::IsMagicValid(magic)) { |
| // The dex file can be a plain dex file. This is expected. |
| return File(); |
| } |
| return Error() << "File is neither a zip file nor a plain dex file"; |
| } |
| |
| std::unique_ptr<ZipArchive> zip_archive( |
| ZipArchive::OpenFromOwnedFd(dex_file->Fd(), dex_path.c_str(), &error_msg)); |
| if (zip_archive == nullptr) { |
| return Error() << error_msg; |
| } |
| constexpr const char* kEmbeddedProfileEntry = "assets/art-profile/baseline.prof"; |
| std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(kEmbeddedProfileEntry, &error_msg)); |
| size_t size; |
| if (zip_entry == nullptr || (size = zip_entry->GetUncompressedLength()) == 0) { |
| // From system/libziparchive/zip_error.cpp. |
| constexpr const char* kEntryNotFound = "Entry not found"; |
| if (error_msg != kEntryNotFound) { |
| LOG(WARNING) << ART_FORMAT( |
| "Failed to find zip entry '{}' in '{}': {}", kEmbeddedProfileEntry, dex_path, error_msg); |
| } |
| // The dex file doesn't necessarily contain a profile. This is expected. |
| return File(); |
| } |
| |
| // The name is for debugging only. |
| std::string memfd_name = |
| ART_FORMAT("{} extracted in memory from {}", kEmbeddedProfileEntry, dex_path); |
| File memfd(memfd_create(memfd_name.c_str(), /*flags=*/0), |
| memfd_name, |
| /*check_usage=*/false); |
| if (!memfd.IsValid()) { |
| return ErrnoError() << "Failed to create memfd"; |
| } |
| if (ftruncate(memfd.Fd(), size) != 0) { |
| return ErrnoError() << "Failed to ftruncate memfd"; |
| } |
| // Map with MAP_SHARED because we're feeding the fd to profman. |
| MemMap mem_map = MemMap::MapFile(size, |
| PROT_READ | PROT_WRITE, |
| MAP_SHARED, |
| memfd.Fd(), |
| /*start=*/0, |
| /*low_4gb=*/false, |
| memfd_name.c_str(), |
| &error_msg); |
| if (!mem_map.IsValid()) { |
| return Errorf("Failed to mmap memfd: {}", error_msg); |
| } |
| if (!zip_entry->ExtractToMemory(mem_map.Begin(), &error_msg)) { |
| return Errorf("Failed to extract '{}': {}", kEmbeddedProfileEntry, error_msg); |
| } |
| |
| // Reopen the memfd with readonly to make SELinux happy when the fd is passed to a child process |
| // who doesn't have write permission. (b/303909581) |
| std::string path = ART_FORMAT("/proc/self/fd/{}", memfd.Fd()); |
| // NOLINTNEXTLINE - O_CLOEXEC is omitted on purpose |
| File memfd_readonly(open(path.c_str(), O_RDONLY), |
| memfd_name, |
| /*check_usage=*/false, |
| /*read_only_mode=*/true); |
| if (!memfd_readonly.IsOpened()) { |
| return ErrnoErrorf("Failed to open file '{}' ('{}')", path, memfd_name); |
| } |
| |
| return memfd_readonly; |
| } |
| |
| class FdLogger { |
| public: |
| void Add(const NewFile& file) { fd_mapping_.emplace_back(file.Fd(), file.TempPath()); } |
| void Add(const File& file) { fd_mapping_.emplace_back(file.Fd(), file.GetPath()); } |
| |
| std::string GetFds() { |
| std::vector<int> fds; |
| fds.reserve(fd_mapping_.size()); |
| for (const auto& [fd, path] : fd_mapping_) { |
| fds.push_back(fd); |
| } |
| return Join(fds, ':'); |
| } |
| |
| private: |
| std::vector<std::pair<int, std::string>> fd_mapping_; |
| |
| friend std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger); |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, const FdLogger& fd_logger) { |
| for (const auto& [fd, path] : fd_logger.fd_mapping_) { |
| os << fd << ":" << path << ' '; |
| } |
| return os; |
| } |
| |
| } // namespace |
| |
| #define RETURN_FATAL_IF_PRE_REBOOT(options) \ |
| if ((options).is_pre_reboot) { \ |
| return Fatal("This method is not supported in Pre-reboot Dexopt mode"); \ |
| } |
| |
| #define RETURN_FATAL_IF_NOT_PRE_REBOOT(options) \ |
| if (!(options).is_pre_reboot) { \ |
| return Fatal("This method is only supported in Pre-reboot Dexopt mode"); \ |
| } |
| |
| #define RETURN_FATAL_IF_ARG_IS_PRE_REBOOT_IMPL(expected, arg, log_name) \ |
| { \ |
| auto&& __return_fatal_tmp = PreRebootFlag(arg); \ |
| if ((expected) != __return_fatal_tmp) { \ |
| return Fatal(ART_FORMAT("Expected flag 'isPreReboot' in argument '{}' to be {}, got {}", \ |
| log_name, \ |
| expected, \ |
| __return_fatal_tmp)); \ |
| } \ |
| } |
| |
| #define RETURN_FATAL_IF_PRE_REBOOT_MISMATCH(options, arg, log_name) \ |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT_IMPL((options).is_pre_reboot, arg, log_name) |
| |
| #define RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(arg, log_name) \ |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT_IMPL(false, arg, log_name) |
| |
| Result<void> Restorecon( |
| const std::string& path, |
| const std::optional<OutputArtifacts::PermissionSettings::SeContext>& se_context, |
| bool recurse) { |
| if (!kIsTargetAndroid) { |
| return {}; |
| } |
| |
| unsigned int flags = recurse ? SELINUX_ANDROID_RESTORECON_RECURSE : 0; |
| int res = 0; |
| if (se_context.has_value()) { |
| res = selinux_android_restorecon_pkgdir( |
| path.c_str(), se_context->seInfo.c_str(), se_context->uid, flags); |
| } else { |
| res = selinux_android_restorecon(path.c_str(), flags); |
| } |
| if (res != 0) { |
| return ErrnoErrorf("Failed to restorecon directory '{}'", path); |
| } |
| return {}; |
| } |
| |
| ScopedAStatus Artd::isAlive(bool* _aidl_return) { |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::deleteArtifacts(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_artifactsPath, "artifactsPath"); |
| |
| RawArtifactsPath path = OR_RETURN_FATAL(BuildArtifactsPath(in_artifactsPath)); |
| |
| *_aidl_return = 0; |
| *_aidl_return += GetSizeAndDeleteFile(path.oat_path); |
| *_aidl_return += GetSizeAndDeleteFile(path.vdex_path); |
| *_aidl_return += GetSizeAndDeleteFile(path.art_path); |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::getDexoptStatus(const std::string& in_dexFile, |
| const std::string& in_instructionSet, |
| const std::optional<std::string>& in_classLoaderContext, |
| GetDexoptStatusResult* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| |
| Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext(); |
| if (!ofa_context.ok()) { |
| return NonFatal("Failed to get runtime options: " + ofa_context.error().message()); |
| } |
| |
| std::unique_ptr<ClassLoaderContext> context; |
| std::string error_msg; |
| auto oat_file_assistant = OatFileAssistant::Create(in_dexFile, |
| in_instructionSet, |
| in_classLoaderContext, |
| /*load_executable=*/false, |
| /*only_load_trusted_executable=*/true, |
| ofa_context.value(), |
| &context, |
| &error_msg); |
| if (oat_file_assistant == nullptr) { |
| return NonFatal("Failed to create OatFileAssistant: " + error_msg); |
| } |
| |
| std::string ignored_odex_status; |
| OatFileAssistant::Location location; |
| oat_file_assistant->GetOptimizationStatus(&_aidl_return->locationDebugString, |
| &_aidl_return->compilerFilter, |
| &_aidl_return->compilationReason, |
| &ignored_odex_status, |
| &location); |
| _aidl_return->artifactsLocation = ArtifactsLocationToAidl(location); |
| |
| // We ignore odex_status because it is not meaningful. It can only be either "up-to-date", |
| // "apk-more-recent", or "io-error-no-oat", which means it doesn't give us information in addition |
| // to what we can learn from compiler_filter because compiler_filter will be the actual compiler |
| // filter, "run-from-apk-fallback", and "run-from-apk" in those three cases respectively. |
| DCHECK(ignored_odex_status == "up-to-date" || ignored_odex_status == "apk-more-recent" || |
| ignored_odex_status == "io-error-no-oat"); |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::isProfileUsable(const ProfilePath& in_profile, |
| const std::string& in_dexFile, |
| bool* _aidl_return) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_profile, "profile"); |
| |
| std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); |
| OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); |
| |
| FdLogger fd_logger; |
| |
| CmdlineBuilder art_exec_args = OR_RETURN_FATAL(GetArtExecCmdlineBuilder()); |
| |
| CmdlineBuilder args; |
| args.Add(OR_RETURN_FATAL(GetProfman())); |
| |
| Result<std::unique_ptr<File>> profile = OpenFileForReading(profile_path); |
| if (!profile.ok()) { |
| if (profile.error().code() == ENOENT) { |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| return NonFatal( |
| ART_FORMAT("Failed to open profile '{}': {}", profile_path, profile.error().message())); |
| } |
| args.Add("--reference-profile-file-fd=%d", profile.value()->Fd()); |
| fd_logger.Add(*profile.value()); |
| |
| std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); |
| args.Add("--apk-fd=%d", dex_file->Fd()); |
| fd_logger.Add(*dex_file); |
| |
| art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); |
| |
| LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ") |
| << "\nOpened FDs: " << fd_logger; |
| |
| Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec); |
| if (!result.ok()) { |
| return NonFatal("Failed to run profman: " + result.error().message()); |
| } |
| |
| LOG(INFO) << ART_FORMAT("profman returned code {}", result.value()); |
| |
| if (result.value() != ProfmanResult::kSkipCompilationSmallDelta && |
| result.value() != ProfmanResult::kSkipCompilationEmptyProfiles) { |
| return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", result.value())); |
| } |
| |
| *_aidl_return = result.value() == ProfmanResult::kSkipCompilationSmallDelta; |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::CopyAndRewriteProfileImpl(File src, |
| OutputProfile* dst_aidl, |
| const std::string& dex_path, |
| CopyAndRewriteProfileResult* aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT_MISMATCH(options_, *dst_aidl, "dst"); |
| std::string dst_path = OR_RETURN_FATAL(BuildFinalProfilePath(dst_aidl->profilePath)); |
| OR_RETURN_FATAL(ValidateDexPath(dex_path)); |
| |
| FdLogger fd_logger; |
| |
| CmdlineBuilder art_exec_args = OR_RETURN_FATAL(GetArtExecCmdlineBuilder()); |
| |
| CmdlineBuilder args; |
| args.Add(OR_RETURN_FATAL(GetProfman())).Add("--copy-and-update-profile-key"); |
| |
| args.Add("--profile-file-fd=%d", src.Fd()); |
| fd_logger.Add(src); |
| |
| std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path)); |
| args.Add("--apk-fd=%d", dex_file->Fd()); |
| fd_logger.Add(*dex_file); |
| |
| std::unique_ptr<NewFile> dst = |
| OR_RETURN_NON_FATAL(NewFile::Create(dst_path, dst_aidl->fsPermission)); |
| args.Add("--reference-profile-file-fd=%d", dst->Fd()); |
| fd_logger.Add(*dst); |
| |
| art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); |
| |
| LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ") |
| << "\nOpened FDs: " << fd_logger; |
| |
| Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec); |
| if (!result.ok()) { |
| return NonFatal("Failed to run profman: " + result.error().message()); |
| } |
| |
| LOG(INFO) << ART_FORMAT("profman returned code {}", result.value()); |
| |
| if (result.value() == ProfmanResult::kCopyAndUpdateNoMatch || |
| result.value() == ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile) { |
| *aidl_return = AnalyzeCopyAndRewriteProfileFailure( |
| &src, static_cast<ProfmanResult::CopyAndUpdateResult>(result.value())); |
| return ScopedAStatus::ok(); |
| } |
| |
| if (result.value() != ProfmanResult::kCopyAndUpdateSuccess) { |
| return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", result.value())); |
| } |
| |
| OR_RETURN_NON_FATAL(dst->Keep()); |
| aidl_return->status = CopyAndRewriteProfileResult::Status::SUCCESS; |
| dst_aidl->profilePath.id = dst->TempId(); |
| dst_aidl->profilePath.tmpPath = dst->TempPath(); |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::copyAndRewriteProfile(const ProfilePath& in_src, |
| OutputProfile* in_dst, |
| const std::string& in_dexFile, |
| CopyAndRewriteProfileResult* _aidl_return) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_src, "src"); |
| |
| std::string src_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_src)); |
| |
| Result<std::unique_ptr<File>> src = OpenFileForReading(src_path); |
| if (!src.ok()) { |
| if (src.error().code() == ENOENT) { |
| _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE; |
| return ScopedAStatus::ok(); |
| } |
| return NonFatal( |
| ART_FORMAT("Failed to open src profile '{}': {}", src_path, src.error().message())); |
| } |
| |
| return CopyAndRewriteProfileImpl(std::move(*src.value()), in_dst, in_dexFile, _aidl_return); |
| } |
| |
| ndk::ScopedAStatus Artd::copyAndRewriteEmbeddedProfile(OutputProfile* in_dst, |
| const std::string& in_dexFile, |
| CopyAndRewriteProfileResult* _aidl_return) { |
| OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); |
| |
| Result<File> src = ExtractEmbeddedProfileToFd(in_dexFile); |
| if (!src.ok()) { |
| return NonFatal(ART_FORMAT( |
| "Failed to extract profile from dex file '{}': {}", in_dexFile, src.error().message())); |
| } |
| if (!src->IsValid()) { |
| _aidl_return->status = CopyAndRewriteProfileResult::Status::NO_PROFILE; |
| return ScopedAStatus::ok(); |
| } |
| |
| return CopyAndRewriteProfileImpl(std::move(src.value()), in_dst, in_dexFile, _aidl_return); |
| } |
| |
| ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) { |
| RETURN_FATAL_IF_PRE_REBOOT_MISMATCH(options_, in_profile, "profile"); |
| std::string tmp_profile_path = OR_RETURN_FATAL(BuildTmpProfilePath(in_profile)); |
| std::string ref_profile_path = OR_RETURN_FATAL(BuildFinalProfilePath(in_profile)); |
| |
| std::error_code ec; |
| std::filesystem::rename(tmp_profile_path, ref_profile_path, ec); |
| if (ec) { |
| return NonFatal(ART_FORMAT( |
| "Failed to move '{}' to '{}': {}", tmp_profile_path, ref_profile_path, ec.message())); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) { |
| // `in_profile` can be either a Pre-reboot path or an ordinary one. |
| std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); |
| DeleteFile(profile_path); |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile, |
| FileVisibility* _aidl_return) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_profile, "profile"); |
| std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); |
| *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(profile_path)); |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath, |
| FileVisibility* _aidl_return) { |
| // `in_artifactsPath` can be either a Pre-reboot path or an ordinary one. |
| std::string oat_path = OR_RETURN_FATAL(BuildArtifactsPath(in_artifactsPath)).oat_path; |
| *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path)); |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::getDexFileVisibility(const std::string& in_dexFile, |
| FileVisibility* _aidl_return) { |
| OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); |
| *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(in_dexFile)); |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::getDmFileVisibility(const DexMetadataPath& in_dmFile, |
| FileVisibility* _aidl_return) { |
| std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile)); |
| *_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(dm_path)); |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profiles, |
| const std::optional<ProfilePath>& in_referenceProfile, |
| OutputProfile* in_outputProfile, |
| const std::vector<std::string>& in_dexFiles, |
| const MergeProfileOptions& in_options, |
| bool* _aidl_return) { |
| std::vector<std::string> profile_paths; |
| for (const ProfilePath& profile : in_profiles) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(profile, "profiles"); |
| std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(profile)); |
| if (profile.getTag() == ProfilePath::dexMetadataPath) { |
| return Fatal(ART_FORMAT("Does not support DM file, got '{}'", profile_path)); |
| } |
| profile_paths.push_back(std::move(profile_path)); |
| } |
| |
| RETURN_FATAL_IF_PRE_REBOOT_MISMATCH(options_, *in_outputProfile, "outputProfile"); |
| std::string output_profile_path = |
| OR_RETURN_FATAL(BuildFinalProfilePath(in_outputProfile->profilePath)); |
| for (const std::string& dex_file : in_dexFiles) { |
| OR_RETURN_FATAL(ValidateDexPath(dex_file)); |
| } |
| if (in_options.forceMerge + in_options.dumpOnly + in_options.dumpClassesAndMethods > 1) { |
| return Fatal("Only one of 'forceMerge', 'dumpOnly', and 'dumpClassesAndMethods' can be set"); |
| } |
| |
| FdLogger fd_logger; |
| |
| CmdlineBuilder art_exec_args = OR_RETURN_FATAL(GetArtExecCmdlineBuilder()); |
| |
| CmdlineBuilder args; |
| args.Add(OR_RETURN_FATAL(GetProfman())); |
| |
| std::vector<std::unique_ptr<File>> profile_files; |
| for (const std::string& profile_path : profile_paths) { |
| Result<std::unique_ptr<File>> profile_file = OpenFileForReading(profile_path); |
| if (!profile_file.ok()) { |
| if (profile_file.error().code() == ENOENT) { |
| // Skip non-existing file. |
| continue; |
| } |
| return NonFatal(ART_FORMAT( |
| "Failed to open profile '{}': {}", profile_path, profile_file.error().message())); |
| } |
| args.Add("--profile-file-fd=%d", profile_file.value()->Fd()); |
| fd_logger.Add(*profile_file.value()); |
| profile_files.push_back(std::move(profile_file.value())); |
| } |
| |
| if (profile_files.empty()) { |
| LOG(INFO) << "Merge skipped because there are no existing profiles"; |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| |
| std::unique_ptr<NewFile> output_profile_file = |
| OR_RETURN_NON_FATAL(NewFile::Create(output_profile_path, in_outputProfile->fsPermission)); |
| |
| if (in_referenceProfile.has_value()) { |
| if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { |
| return Fatal( |
| "Reference profile must not be set when 'dumpOnly' or 'dumpClassesAndMethods' is set"); |
| } |
| // `in_referenceProfile` can be either a Pre-reboot profile or an ordinary one. |
| std::string reference_profile_path = |
| OR_RETURN_FATAL(BuildProfileOrDmPath(*in_referenceProfile)); |
| if (in_referenceProfile->getTag() == ProfilePath::dexMetadataPath) { |
| return Fatal(ART_FORMAT("Does not support DM file, got '{}'", reference_profile_path)); |
| } |
| OR_RETURN_NON_FATAL(CopyFile(reference_profile_path, *output_profile_file)); |
| } |
| |
| if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { |
| args.Add("--dump-output-to-fd=%d", output_profile_file->Fd()); |
| } else { |
| // profman is ok with this being an empty file when in_referenceProfile isn't set. |
| args.Add("--reference-profile-file-fd=%d", output_profile_file->Fd()); |
| } |
| fd_logger.Add(*output_profile_file); |
| |
| std::vector<std::unique_ptr<File>> dex_files; |
| for (const std::string& dex_path : in_dexFiles) { |
| std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(dex_path)); |
| args.Add("--apk-fd=%d", dex_file->Fd()); |
| fd_logger.Add(*dex_file); |
| dex_files.push_back(std::move(dex_file)); |
| } |
| |
| if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { |
| args.Add(in_options.dumpOnly ? "--dump-only" : "--dump-classes-and-methods"); |
| } else { |
| args.AddIfNonEmpty("--min-new-classes-percent-change=%s", |
| props_->GetOrEmpty("dalvik.vm.bgdexopt.new-classes-percent")) |
| .AddIfNonEmpty("--min-new-methods-percent-change=%s", |
| props_->GetOrEmpty("dalvik.vm.bgdexopt.new-methods-percent")) |
| .AddIf(in_options.forceMerge, "--force-merge-and-analyze") |
| .AddIf(in_options.forBootImage, "--boot-image-merge"); |
| } |
| |
| art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); |
| |
| LOG(INFO) << "Running profman: " << Join(art_exec_args.Get(), /*separator=*/" ") |
| << "\nOpened FDs: " << fd_logger; |
| |
| Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kShortTimeoutSec); |
| if (!result.ok()) { |
| return NonFatal("Failed to run profman: " + result.error().message()); |
| } |
| |
| LOG(INFO) << ART_FORMAT("profman returned code {}", result.value()); |
| |
| if (result.value() == ProfmanResult::kSkipCompilationSmallDelta || |
| result.value() == ProfmanResult::kSkipCompilationEmptyProfiles) { |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| |
| ProfmanResult::ProcessingResult expected_result = |
| (in_options.dumpOnly || in_options.dumpClassesAndMethods) ? ProfmanResult::kSuccess : |
| ProfmanResult::kCompile; |
| if (result.value() != expected_result) { |
| return NonFatal(ART_FORMAT("profman returned an unexpected code: {}", result.value())); |
| } |
| |
| OR_RETURN_NON_FATAL(output_profile_file->Keep()); |
| *_aidl_return = true; |
| in_outputProfile->profilePath.id = output_profile_file->TempId(); |
| in_outputProfile->profilePath.tmpPath = output_profile_file->TempPath(); |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile, |
| const std::string& in_instructionSet, |
| const std::optional<std::string>& in_classLoaderContext, |
| const std::string& in_compilerFilter, |
| int32_t in_dexoptTrigger, |
| GetDexoptNeededResult* _aidl_return) { |
| Result<OatFileAssistantContext*> ofa_context = GetOatFileAssistantContext(); |
| if (!ofa_context.ok()) { |
| return NonFatal("Failed to get runtime options: " + ofa_context.error().message()); |
| } |
| |
| std::unique_ptr<ClassLoaderContext> context; |
| std::string error_msg; |
| auto oat_file_assistant = OatFileAssistant::Create(in_dexFile, |
| in_instructionSet, |
| in_classLoaderContext, |
| /*load_executable=*/false, |
| /*only_load_trusted_executable=*/true, |
| ofa_context.value(), |
| &context, |
| &error_msg); |
| if (oat_file_assistant == nullptr) { |
| return NonFatal("Failed to create OatFileAssistant: " + error_msg); |
| } |
| |
| OatFileAssistant::DexOptStatus status; |
| _aidl_return->isDexoptNeeded = |
| oat_file_assistant->GetDexOptNeeded(OR_RETURN_FATAL(ParseCompilerFilter(in_compilerFilter)), |
| DexOptTriggerFromAidl(in_dexoptTrigger), |
| &status); |
| _aidl_return->isVdexUsable = status.IsVdexUsable(); |
| _aidl_return->artifactsLocation = ArtifactsLocationToAidl(status.GetLocation()); |
| |
| std::optional<bool> has_dex_files = oat_file_assistant->HasDexFiles(&error_msg); |
| if (!has_dex_files.has_value()) { |
| return NonFatal("Failed to open dex file: " + error_msg); |
| } |
| _aidl_return->hasDexCode = *has_dex_files; |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus Artd::dexopt( |
| const OutputArtifacts& in_outputArtifacts, |
| const std::string& in_dexFile, |
| const std::string& in_instructionSet, |
| const std::optional<std::string>& in_classLoaderContext, |
| const std::string& in_compilerFilter, |
| const std::optional<ProfilePath>& in_profile, |
| const std::optional<VdexPath>& in_inputVdex, |
| const std::optional<DexMetadataPath>& in_dmFile, |
| PriorityClass in_priorityClass, |
| const DexoptOptions& in_dexoptOptions, |
| const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal, |
| ArtdDexoptResult* _aidl_return) { |
| _aidl_return->cancelled = false; |
| |
| RETURN_FATAL_IF_PRE_REBOOT_MISMATCH(options_, in_outputArtifacts, "outputArtifacts"); |
| RawArtifactsPath artifacts_path = |
| OR_RETURN_FATAL(BuildArtifactsPath(in_outputArtifacts.artifactsPath)); |
| OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); |
| // `in_profile` can be either a Pre-reboot profile or an ordinary one. |
| std::optional<std::string> profile_path = |
| in_profile.has_value() ? |
| std::make_optional(OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile.value()))) : |
| std::nullopt; |
| ArtdCancellationSignal* cancellation_signal = |
| OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get())); |
| |
| std::unique_ptr<ClassLoaderContext> context = nullptr; |
| if (in_classLoaderContext.has_value()) { |
| context = ClassLoaderContext::Create(in_classLoaderContext.value()); |
| if (context == nullptr) { |
| return Fatal( |
| ART_FORMAT("Class loader context '{}' is invalid", in_classLoaderContext.value())); |
| } |
| } |
| |
| std::string oat_dir_path; // For restorecon, can be empty if the artifacts are in dalvik-cache. |
| OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts, &oat_dir_path)); |
| |
| // First-round restorecon. artd doesn't have the permission to create files with the |
| // `apk_data_file` label, so we need to restorecon the "oat" directory first so that files will |
| // inherit `dalvikcache_data_file` rather than `apk_data_file`. |
| if (!in_outputArtifacts.artifactsPath.isInDalvikCache) { |
| OR_RETURN_NON_FATAL(restorecon_( |
| oat_dir_path, in_outputArtifacts.permissionSettings.seContext, /*recurse=*/true)); |
| } |
| |
| FdLogger fd_logger; |
| |
| CmdlineBuilder art_exec_args = OR_RETURN_FATAL(GetArtExecCmdlineBuilder()); |
| |
| CmdlineBuilder args; |
| args.Add(OR_RETURN_FATAL(GetDex2Oat())); |
| |
| const FsPermission& fs_permission = in_outputArtifacts.permissionSettings.fileFsPermission; |
| |
| std::unique_ptr<File> dex_file = OR_RETURN_NON_FATAL(OpenFileForReading(in_dexFile)); |
| args.Add("--zip-fd=%d", dex_file->Fd()).Add("--zip-location=%s", in_dexFile); |
| fd_logger.Add(*dex_file); |
| struct stat dex_st = OR_RETURN_NON_FATAL(Fstat(*dex_file)); |
| if ((dex_st.st_mode & S_IROTH) == 0) { |
| if (fs_permission.isOtherReadable) { |
| return NonFatal(ART_FORMAT( |
| "Outputs cannot be other-readable because the dex file '{}' is not other-readable", |
| dex_file->GetPath())); |
| } |
| // Negative numbers mean no `chown`. 0 means root. |
| // Note: this check is more strict than it needs to be. For example, it doesn't allow the |
| // outputs to belong to a group that is a subset of the dex file's group. This is for |
| // simplicity, and it's okay as we don't have to handle such complicated cases in practice. |
| if ((fs_permission.uid > 0 && static_cast<uid_t>(fs_permission.uid) != dex_st.st_uid) || |
| (fs_permission.gid > 0 && static_cast<gid_t>(fs_permission.gid) != dex_st.st_uid && |
| static_cast<gid_t>(fs_permission.gid) != dex_st.st_gid)) { |
| return NonFatal(ART_FORMAT( |
| "Outputs' owner doesn't match the dex file '{}' (outputs: {}:{}, dex file: {}:{})", |
| dex_file->GetPath(), |
| fs_permission.uid, |
| fs_permission.gid, |
| dex_st.st_uid, |
| dex_st.st_gid)); |
| } |
| } |
| |
| std::unique_ptr<NewFile> oat_file = |
| OR_RETURN_NON_FATAL(NewFile::Create(artifacts_path.oat_path, fs_permission)); |
| args.Add("--oat-fd=%d", oat_file->Fd()).Add("--oat-location=%s", artifacts_path.oat_path); |
| fd_logger.Add(*oat_file); |
| |
| std::unique_ptr<NewFile> vdex_file = |
| OR_RETURN_NON_FATAL(NewFile::Create(artifacts_path.vdex_path, fs_permission)); |
| args.Add("--output-vdex-fd=%d", vdex_file->Fd()); |
| fd_logger.Add(*vdex_file); |
| |
| std::vector<NewFile*> files_to_commit{oat_file.get(), vdex_file.get()}; |
| std::vector<std::string_view> files_to_delete; |
| |
| std::unique_ptr<NewFile> art_file = nullptr; |
| if (in_dexoptOptions.generateAppImage) { |
| art_file = OR_RETURN_NON_FATAL(NewFile::Create(artifacts_path.art_path, fs_permission)); |
| args.Add("--app-image-fd=%d", art_file->Fd()); |
| args.AddIfNonEmpty("--image-format=%s", props_->GetOrEmpty("dalvik.vm.appimageformat")); |
| fd_logger.Add(*art_file); |
| files_to_commit.push_back(art_file.get()); |
| } else { |
| files_to_delete.push_back(artifacts_path.art_path); |
| } |
| |
| std::unique_ptr<NewFile> swap_file = nullptr; |
| if (ShouldCreateSwapFileForDexopt()) { |
| std::string swap_file_path = ART_FORMAT("{}.swap", artifacts_path.oat_path); |
| swap_file = |
| OR_RETURN_NON_FATAL(NewFile::Create(swap_file_path, FsPermission{.uid = -1, .gid = -1})); |
| args.Add("--swap-fd=%d", swap_file->Fd()); |
| fd_logger.Add(*swap_file); |
| } |
| |
| std::vector<std::unique_ptr<File>> context_files; |
| if (context != nullptr) { |
| std::vector<std::string> flattened_context = context->FlattenDexPaths(); |
| std::string dex_dir = Dirname(in_dexFile); |
| std::vector<int> context_fds; |
| for (const std::string& context_element : flattened_context) { |
| std::string context_path = std::filesystem::path(dex_dir).append(context_element); |
| OR_RETURN_FATAL(ValidateDexPath(context_path)); |
| std::unique_ptr<File> context_file = OR_RETURN_NON_FATAL(OpenFileForReading(context_path)); |
| context_fds.push_back(context_file->Fd()); |
| fd_logger.Add(*context_file); |
| context_files.push_back(std::move(context_file)); |
| } |
| args.AddIfNonEmpty("--class-loader-context-fds=%s", Join(context_fds, /*separator=*/':')) |
| .Add("--class-loader-context=%s", in_classLoaderContext.value()) |
| .Add("--classpath-dir=%s", dex_dir); |
| } |
| |
| std::unique_ptr<File> input_vdex_file = nullptr; |
| if (in_inputVdex.has_value()) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_inputVdex.value(), "inputVdex"); |
| std::string input_vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_inputVdex.value())); |
| input_vdex_file = OR_RETURN_NON_FATAL(OpenFileForReading(input_vdex_path)); |
| args.Add("--input-vdex-fd=%d", input_vdex_file->Fd()); |
| fd_logger.Add(*input_vdex_file); |
| } |
| |
| std::unique_ptr<File> dm_file = nullptr; |
| if (in_dmFile.has_value()) { |
| std::string dm_path = OR_RETURN_FATAL(BuildDexMetadataPath(in_dmFile.value())); |
| dm_file = OR_RETURN_NON_FATAL(OpenFileForReading(dm_path)); |
| args.Add("--dm-fd=%d", dm_file->Fd()); |
| fd_logger.Add(*dm_file); |
| } |
| |
| std::unique_ptr<File> profile_file = nullptr; |
| if (profile_path.has_value()) { |
| profile_file = OR_RETURN_NON_FATAL(OpenFileForReading(profile_path.value())); |
| args.Add("--profile-file-fd=%d", profile_file->Fd()); |
| fd_logger.Add(*profile_file); |
| struct stat profile_st = OR_RETURN_NON_FATAL(Fstat(*profile_file)); |
| if (fs_permission.isOtherReadable && (profile_st.st_mode & S_IROTH) == 0) { |
| return NonFatal(ART_FORMAT( |
| "Outputs cannot be other-readable because the profile '{}' is not other-readable", |
| profile_file->GetPath())); |
| } |
| // TODO(b/260228411): Check uid and gid. |
| } |
| |
| // Second-round restorecon. Restorecon recursively after the output files are created, so that the |
| // SELinux context is applied to all of them. The SELinux context of a file is mostly inherited |
| // from the parent directory upon creation, but the MLS label is not inherited, so we need to |
| // restorecon every file so that they have the right MLS label. If the files are in dalvik-cache, |
| // there's no need to restorecon because they inherits the SELinux context of the dalvik-cache |
| // directory and they don't need to have MLS labels. |
| if (!in_outputArtifacts.artifactsPath.isInDalvikCache) { |
| OR_RETURN_NON_FATAL(restorecon_( |
| oat_dir_path, in_outputArtifacts.permissionSettings.seContext, /*recurse=*/true)); |
| } |
| |
| AddBootImageFlags(args); |
| AddCompilerConfigFlags( |
| in_instructionSet, in_compilerFilter, in_priorityClass, in_dexoptOptions, args); |
| AddPerfConfigFlags(in_priorityClass, art_exec_args, args); |
| |
| // For being surfaced in crash reports on crashes. |
| args.Add("--comments=%s", in_dexoptOptions.comments); |
| |
| art_exec_args.Add("--keep-fds=%s", fd_logger.GetFds()).Add("--").Concat(std::move(args)); |
| |
| LOG(INFO) << "Running dex2oat: " << Join(art_exec_args.Get(), /*separator=*/" ") |
| << "\nOpened FDs: " << fd_logger; |
| |
| ProcessStat stat; |
| std::string error_msg; |
| ExecResult result = exec_utils_->ExecAndReturnResult(art_exec_args.Get(), |
| kLongTimeoutSec, |
| cancellation_signal->CreateExecCallbacks(), |
| /*new_process_group=*/true, |
| &stat, |
| &error_msg); |
| _aidl_return->wallTimeMs = stat.wall_time_ms; |
| _aidl_return->cpuTimeMs = stat.cpu_time_ms; |
| |
| auto result_info = ART_FORMAT("[status={},exit_code={},signal={}]", |
| static_cast<int>(result.status), |
| result.exit_code, |
| result.signal); |
| if (result.status != ExecResult::kExited) { |
| if (cancellation_signal->IsCancelled()) { |
| _aidl_return->cancelled = true; |
| return ScopedAStatus::ok(); |
| } |
| return NonFatal(ART_FORMAT("Failed to run dex2oat: {} {}", error_msg, result_info)); |
| } |
| |
| LOG(INFO) << ART_FORMAT("dex2oat returned code {}", result.exit_code); |
| |
| if (result.exit_code != 0) { |
| return NonFatal( |
| ART_FORMAT("dex2oat returned an unexpected code: {} {}", result.exit_code, result_info)); |
| } |
| |
| int64_t size_bytes = 0; |
| int64_t size_before_bytes = 0; |
| for (const NewFile* file : files_to_commit) { |
| size_bytes += GetSize(file->TempPath()).value_or(0); |
| size_before_bytes += GetSize(file->FinalPath()).value_or(0); |
| } |
| for (std::string_view path : files_to_delete) { |
| size_before_bytes += GetSize(path).value_or(0); |
| } |
| OR_RETURN_NON_FATAL(NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete)); |
| |
| _aidl_return->sizeBytes = size_bytes; |
| _aidl_return->sizeBeforeBytes = size_before_bytes; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ArtdCancellationSignal::cancel() { |
| std::lock_guard<std::mutex> lock(mu_); |
| is_cancelled_ = true; |
| for (pid_t pid : pids_) { |
| // Kill the whole process group. |
| int res = kill_(-pid, SIGKILL); |
| DCHECK_EQ(res, 0); |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ArtdCancellationSignal::getType(int64_t* _aidl_return) { |
| *_aidl_return = reinterpret_cast<intptr_t>(kArtdCancellationSignalType); |
| return ScopedAStatus::ok(); |
| } |
| |
| ExecCallbacks ArtdCancellationSignal::CreateExecCallbacks() { |
| return { |
| .on_start = |
| [&](pid_t pid) { |
| std::lock_guard<std::mutex> lock(mu_); |
| pids_.insert(pid); |
| // Handle cancellation signals sent before the process starts. |
| if (is_cancelled_) { |
| int res = kill_(-pid, SIGKILL); |
| DCHECK_EQ(res, 0); |
| } |
| }, |
| .on_end = |
| [&](pid_t pid) { |
| std::lock_guard<std::mutex> lock(mu_); |
| // The pid should no longer receive kill signals sent by `cancellation_signal`. |
| pids_.erase(pid); |
| }, |
| }; |
| } |
| |
| bool ArtdCancellationSignal::IsCancelled() { |
| std::lock_guard<std::mutex> lock(mu_); |
| return is_cancelled_; |
| } |
| |
| ScopedAStatus Artd::createCancellationSignal( |
| std::shared_ptr<IArtdCancellationSignal>* _aidl_return) { |
| *_aidl_return = ndk::SharedRefBase::make<ArtdCancellationSignal>(kill_); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep, |
| const std::vector<ArtifactsPath>& in_artifactsToKeep, |
| const std::vector<VdexPath>& in_vdexFilesToKeep, |
| const std::vector<RuntimeArtifactsPath>& in_runtimeArtifactsToKeep, |
| bool in_keepPreRebootStagedFiles, |
| int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| std::unordered_set<std::string> files_to_keep; |
| for (const ProfilePath& profile : in_profilesToKeep) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(profile, "profilesToKeep"); |
| files_to_keep.insert(OR_RETURN_FATAL(BuildProfileOrDmPath(profile))); |
| } |
| for (const ArtifactsPath& artifacts : in_artifactsToKeep) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(artifacts, "artifactsToKeep"); |
| RawArtifactsPath path = OR_RETURN_FATAL(BuildArtifactsPath(artifacts)); |
| files_to_keep.insert(std::move(path.oat_path)); |
| files_to_keep.insert(std::move(path.vdex_path)); |
| files_to_keep.insert(std::move(path.art_path)); |
| } |
| for (const VdexPath& vdex : in_vdexFilesToKeep) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(vdex, "vdexFilesToKeep"); |
| files_to_keep.insert(OR_RETURN_FATAL(BuildVdexPath(vdex))); |
| } |
| std::string android_data = OR_RETURN_NON_FATAL(GetAndroidDataOrError()); |
| std::string android_expand = OR_RETURN_NON_FATAL(GetAndroidExpandOrError()); |
| for (const RuntimeArtifactsPath& runtime_image_path : in_runtimeArtifactsToKeep) { |
| OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(runtime_image_path)); |
| std::vector<std::string> files = |
| ListRuntimeArtifactsFiles(android_data, android_expand, runtime_image_path); |
| std::move(files.begin(), files.end(), std::inserter(files_to_keep, files_to_keep.end())); |
| } |
| *_aidl_return = 0; |
| for (const std::string& file : ListManagedFiles(android_data, android_expand)) { |
| if (files_to_keep.find(file) == files_to_keep.end() && |
| (!in_keepPreRebootStagedFiles || !IsPreRebootStagedFile(file))) { |
| LOG(INFO) << ART_FORMAT("Cleaning up obsolete file '{}'", file); |
| *_aidl_return += GetSizeAndDeleteFile(file); |
| } |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::cleanUpPreRebootStagedFiles() { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| std::string android_data = OR_RETURN_NON_FATAL(GetAndroidDataOrError()); |
| std::string android_expand = OR_RETURN_NON_FATAL(GetAndroidExpandOrError()); |
| for (const std::string& file : ListManagedFiles(android_data, android_expand)) { |
| if (IsPreRebootStagedFile(file)) { |
| LOG(INFO) << ART_FORMAT("Cleaning up obsolete Pre-reboot staged file '{}'", file); |
| DeleteFile(file); |
| } |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::isInDalvikCache(const std::string& in_dexFile, bool* _aidl_return) { |
| // The artifacts should be in the global dalvik-cache directory if: |
| // (1). the dex file is on a system partition, even if the partition is remounted read-write, |
| // or |
| // (2). the dex file is in any other readonly location. (At the time of writing, this only |
| // include Incremental FS.) |
| // |
| // We cannot rely on access(2) because: |
| // - It doesn't take effective capabilities into account, from which artd gets root access |
| // to the filesystem. |
| // - The `faccessat` variant with the `AT_EACCESS` flag, which takes effective capabilities |
| // into account, is not supported by bionic. |
| |
| OR_RETURN_FATAL(ValidateDexPath(in_dexFile)); |
| |
| std::vector<FstabEntry> entries = OR_RETURN_NON_FATAL(GetProcMountsAncestorsOfPath(in_dexFile)); |
| // The last one controls because `/proc/mounts` reflects the sequence of `mount`. |
| for (auto it = entries.rbegin(); it != entries.rend(); it++) { |
| if (it->fs_type == "overlay") { |
| // Ignore the overlays created by `remount`. |
| continue; |
| } |
| // We need to special-case Incremental FS since it is tagged as read-write while it's actually |
| // not. |
| *_aidl_return = (it->flags & MS_RDONLY) != 0 || it->fs_type == "incremental-fs"; |
| return ScopedAStatus::ok(); |
| } |
| |
| return NonFatal(ART_FORMAT("Fstab entries not found for '{}'", in_dexFile)); |
| } |
| |
| ScopedAStatus Artd::deleteRuntimeArtifacts(const RuntimeArtifactsPath& in_runtimeArtifactsPath, |
| int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(in_runtimeArtifactsPath)); |
| *_aidl_return = 0; |
| std::string android_data = OR_LOG_AND_RETURN_OK(GetAndroidDataOrError()); |
| std::string android_expand = OR_LOG_AND_RETURN_OK(GetAndroidExpandOrError()); |
| for (const std::string& file : |
| ListRuntimeArtifactsFiles(android_data, android_expand, in_runtimeArtifactsPath)) { |
| *_aidl_return += GetSizeAndDeleteFile(file); |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::getArtifactsSize(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_artifactsPath, "artifactsPath"); |
| RawArtifactsPath path = OR_RETURN_FATAL(BuildArtifactsPath(in_artifactsPath)); |
| *_aidl_return = 0; |
| *_aidl_return += GetSize(path.oat_path).value_or(0); |
| *_aidl_return += GetSize(path.vdex_path).value_or(0); |
| *_aidl_return += GetSize(path.art_path).value_or(0); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::getVdexFileSize(const VdexPath& in_vdexPath, int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_vdexPath, "vdexPath"); |
| std::string vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_vdexPath)); |
| *_aidl_return = GetSize(vdex_path).value_or(0); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::getRuntimeArtifactsSize(const RuntimeArtifactsPath& in_runtimeArtifactsPath, |
| int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(in_runtimeArtifactsPath)); |
| *_aidl_return = 0; |
| std::string android_data = OR_LOG_AND_RETURN_OK(GetAndroidDataOrError()); |
| std::string android_expand = OR_LOG_AND_RETURN_OK(GetAndroidExpandOrError()); |
| for (const std::string& file : |
| ListRuntimeArtifactsFiles(android_data, android_expand, in_runtimeArtifactsPath)) { |
| *_aidl_return += GetSize(file).value_or(0); |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::getProfileSize(const ProfilePath& in_profile, int64_t* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_profile, "profile"); |
| std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile)); |
| *_aidl_return = GetSize(profile_path).value_or(0); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::initProfileSaveNotification(const PrimaryCurProfilePath& in_profilePath, |
| int in_pid, |
| std::shared_ptr<IArtdNotification>* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| |
| std::string path = OR_RETURN_FATAL(BuildPrimaryCurProfilePath(in_profilePath)); |
| |
| unique_fd inotify_fd(inotify_init1(IN_NONBLOCK | IN_CLOEXEC)); |
| if (inotify_fd < 0) { |
| return NonFatal(ART_FORMAT("Failed to inotify_init1: {}", strerror(errno))); |
| } |
| |
| // Watch the dir rather than the file itself because profiles are moved in rather than updated in |
| // place. |
| std::string dir = Dirname(path); |
| int wd = inotify_add_watch(inotify_fd, dir.c_str(), IN_MOVED_TO); |
| if (wd < 0) { |
| return NonFatal(ART_FORMAT("Failed to inotify_add_watch '{}': {}", dir, strerror(errno))); |
| } |
| |
| unique_fd pidfd = PidfdOpen(in_pid, /*flags=*/0); |
| if (pidfd < 0) { |
| if (errno == ESRCH) { |
| // The process has gone now. |
| LOG(INFO) << ART_FORMAT("Process exited without sending notification '{}'", path); |
| *_aidl_return = ndk::SharedRefBase::make<ArtdNotification>(); |
| return ScopedAStatus::ok(); |
| } |
| return NonFatal(ART_FORMAT("Failed to pidfd_open {}: {}", in_pid, strerror(errno))); |
| } |
| |
| *_aidl_return = ndk::SharedRefBase::make<ArtdNotification>( |
| poll_, path, std::move(inotify_fd), std::move(pidfd)); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ArtdNotification::wait(int in_timeoutMs, bool* _aidl_return) { |
| auto cleanup = make_scope_guard([&, this] { CleanUp(); }); |
| |
| if (!mu_.try_lock()) { |
| return Fatal("`wait` can be called only once"); |
| } |
| std::lock_guard<std::mutex> lock(mu_, std::adopt_lock); |
| LOG(INFO) << ART_FORMAT("Waiting for notification '{}'", path_); |
| |
| if (is_called_) { |
| return Fatal("`wait` can be called only once"); |
| } |
| is_called_ = true; |
| |
| if (done_) { |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| |
| struct pollfd pollfds[2]{ |
| {.fd = inotify_fd_.get(), .events = POLLIN}, |
| {.fd = pidfd_.get(), .events = POLLIN}, |
| }; |
| |
| constexpr size_t kBufSize = sizeof(struct inotify_event) + NAME_MAX + 1; |
| std::unique_ptr<uint8_t[]> buf(new (std::align_val_t(alignof(struct inotify_event))) |
| uint8_t[kBufSize]); |
| std::string basename = Basename(path_); |
| |
| uint64_t start_time = MilliTime(); |
| int64_t remaining_time_ms = in_timeoutMs; |
| while (remaining_time_ms > 0) { |
| int ret = TEMP_FAILURE_RETRY(poll_(pollfds, arraysize(pollfds), remaining_time_ms)); |
| if (ret < 0) { |
| return NonFatal( |
| ART_FORMAT("Failed to poll to wait for notification '{}': {}", path_, strerror(errno))); |
| } |
| if (ret == 0) { |
| // Timeout. |
| break; |
| } |
| if ((pollfds[0].revents & POLLIN) != 0) { |
| ssize_t len = TEMP_FAILURE_RETRY(read(inotify_fd_, buf.get(), kBufSize)); |
| if (len < 0) { |
| return NonFatal(ART_FORMAT( |
| "Failed to read inotify fd for notification '{}': {}", path_, strerror(errno))); |
| } |
| const struct inotify_event* event; |
| for (uint8_t* ptr = buf.get(); ptr < buf.get() + len; |
| ptr += sizeof(struct inotify_event) + event->len) { |
| event = (const struct inotify_event*)ptr; |
| if (event->len > 0 && event->name == basename) { |
| LOG(INFO) << ART_FORMAT("Received notification '{}'", path_); |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| } |
| remaining_time_ms = in_timeoutMs - (MilliTime() - start_time); |
| continue; |
| } |
| if ((pollfds[1].revents & POLLIN) != 0) { |
| LOG(INFO) << ART_FORMAT("Process exited without sending notification '{}'", path_); |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| LOG(FATAL) << "Unreachable code"; |
| UNREACHABLE(); |
| } |
| |
| LOG(INFO) << ART_FORMAT("Timed out while waiting for notification '{}'", path_); |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| |
| ArtdNotification::~ArtdNotification() { CleanUp(); } |
| |
| void ArtdNotification::CleanUp() { |
| std::lock_guard<std::mutex> lock(mu_); |
| inotify_fd_.reset(); |
| pidfd_.reset(); |
| } |
| |
| ScopedAStatus Artd::commitPreRebootStagedFiles(const std::vector<ArtifactsPath>& in_artifacts, |
| const std::vector<WritableProfilePath>& in_profiles, |
| bool* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| |
| std::vector<std::pair<std::string, std::string>> files_to_move; |
| std::vector<std::string> files_to_remove; |
| |
| for (const ArtifactsPath& artifacts : in_artifacts) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(artifacts, "artifacts"); |
| |
| ArtifactsPath pre_reboot_artifacts = artifacts; |
| pre_reboot_artifacts.isPreReboot = true; |
| |
| auto src_artifacts = std::make_unique<RawArtifactsPath>( |
| OR_RETURN_FATAL(BuildArtifactsPath(pre_reboot_artifacts))); |
| auto dst_artifacts = |
| std::make_unique<RawArtifactsPath>(OR_RETURN_FATAL(BuildArtifactsPath(artifacts))); |
| |
| if (OS::FileExists(src_artifacts->oat_path.c_str())) { |
| files_to_move.emplace_back(src_artifacts->oat_path, dst_artifacts->oat_path); |
| files_to_move.emplace_back(src_artifacts->vdex_path, dst_artifacts->vdex_path); |
| if (OS::FileExists(src_artifacts->art_path.c_str())) { |
| files_to_move.emplace_back(src_artifacts->art_path, dst_artifacts->art_path); |
| } else { |
| files_to_remove.push_back(dst_artifacts->art_path); |
| } |
| } |
| } |
| |
| for (const WritableProfilePath& profile : in_profiles) { |
| RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(profile, "profiles"); |
| |
| WritableProfilePath pre_reboot_profile = profile; |
| PreRebootFlag(pre_reboot_profile) = true; |
| |
| auto src_profile = std::make_unique<std::string>( |
| OR_RETURN_FATAL(BuildWritableProfilePath(pre_reboot_profile))); |
| auto dst_profile = |
| std::make_unique<std::string>(OR_RETURN_FATAL(BuildWritableProfilePath(profile))); |
| |
| if (OS::FileExists(src_profile->c_str())) { |
| files_to_move.emplace_back(*src_profile, *dst_profile); |
| } |
| } |
| |
| OR_RETURN_NON_FATAL(MoveAllOrAbandon(files_to_move, files_to_remove)); |
| |
| for (const auto& [src_path, dst_path] : files_to_move) { |
| LOG(INFO) << ART_FORMAT("Committed Pre-reboot staged file '{}' to '{}'", src_path, dst_path); |
| } |
| |
| *_aidl_return = !files_to_move.empty(); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::checkPreRebootSystemRequirements(const std::string& in_chrootDir, |
| bool* _aidl_return) { |
| RETURN_FATAL_IF_PRE_REBOOT(options_); |
| BuildSystemProperties new_props = |
| OR_RETURN_NON_FATAL(BuildSystemProperties::Create(in_chrootDir + "/system/build.prop")); |
| std::string old_release_str = props_->GetOrEmpty("ro.build.version.release"); |
| int old_release; |
| if (!ParseInt(old_release_str, &old_release)) { |
| return NonFatal( |
| ART_FORMAT("Failed to read or parse old release number, got '{}'", old_release_str)); |
| } |
| std::string new_release_str = new_props.GetOrEmpty("ro.build.version.release"); |
| int new_release; |
| if (!ParseInt(new_release_str, &new_release)) { |
| return NonFatal( |
| ART_FORMAT("Failed to read or parse new release number, got '{}'", new_release_str)); |
| } |
| if (new_release - old_release >= 2) { |
| // When the release version difference is large, there is no particular technical reason why we |
| // can't run Pre-reboot Dexopt, but we cannot test and support those cases. |
| LOG(WARNING) << ART_FORMAT( |
| "Pre-reboot Dexopt not supported due to large difference in release versions (old_release: " |
| "{}, new_release: {})", |
| old_release, |
| new_release); |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| |
| Result<void> Artd::Start() { |
| OR_RETURN(SetLogVerbosity()); |
| MemMap::Init(); |
| Runtime::AllowPageSizeAccess(); |
| |
| ScopedAStatus status = ScopedAStatus::fromStatus(AServiceManager_registerLazyService( |
| this->asBinder().get(), options_.is_pre_reboot ? kPreRebootServiceName : kServiceName)); |
| if (!status.isOk()) { |
| return Error() << status.getDescription(); |
| } |
| |
| ABinderProcess_startThreadPool(); |
| |
| return {}; |
| } |
| |
| Result<OatFileAssistantContext*> Artd::GetOatFileAssistantContext() { |
| std::lock_guard<std::mutex> lock(ofa_context_mu_); |
| |
| if (ofa_context_ == nullptr) { |
| ofa_context_ = std::make_unique<OatFileAssistantContext>( |
| std::make_unique<OatFileAssistantContext::RuntimeOptions>( |
| OatFileAssistantContext::RuntimeOptions{ |
| .image_locations = *OR_RETURN(GetBootImageLocations()), |
| .boot_class_path = *OR_RETURN(GetBootClassPath()), |
| .boot_class_path_locations = *OR_RETURN(GetBootClassPath()), |
| .deny_art_apex_data_files = DenyArtApexDataFiles(), |
| })); |
| std::string error_msg; |
| if (!ofa_context_->FetchAll(&error_msg)) { |
| return Error() << error_msg; |
| } |
| } |
| |
| return ofa_context_.get(); |
| } |
| |
| Result<const std::vector<std::string>*> Artd::GetBootImageLocations() { |
| std::lock_guard<std::mutex> lock(cache_mu_); |
| |
| if (!cached_boot_image_locations_.has_value()) { |
| std::string location_str; |
| |
| if (UseJitZygoteLocked()) { |
| location_str = GetJitZygoteBootImageLocation(); |
| } else if (std::string value = GetUserDefinedBootImageLocationsLocked(); !value.empty()) { |
| location_str = std::move(value); |
| } else { |
| std::string error_msg; |
| std::string android_root = GetAndroidRootSafe(&error_msg); |
| if (!error_msg.empty()) { |
| return Errorf("Failed to get ANDROID_ROOT: {}", error_msg); |
| } |
| location_str = GetDefaultBootImageLocation(android_root, DenyArtApexDataFilesLocked()); |
| } |
| |
| cached_boot_image_locations_ = Split(location_str, ":"); |
| } |
| |
| return &cached_boot_image_locations_.value(); |
| } |
| |
| Result<const std::vector<std::string>*> Artd::GetBootClassPath() { |
| std::lock_guard<std::mutex> lock(cache_mu_); |
| |
| if (!cached_boot_class_path_.has_value()) { |
| const char* env_value = getenv("BOOTCLASSPATH"); |
| if (env_value == nullptr || strlen(env_value) == 0) { |
| return Errorf("Failed to get environment variable 'BOOTCLASSPATH'"); |
| } |
| cached_boot_class_path_ = Split(env_value, ":"); |
| } |
| |
| return &cached_boot_class_path_.value(); |
| } |
| |
| bool Artd::UseJitZygote() { |
| std::lock_guard<std::mutex> lock(cache_mu_); |
| return UseJitZygoteLocked(); |
| } |
| |
| bool Artd::UseJitZygoteLocked() { |
| if (!cached_use_jit_zygote_.has_value()) { |
| cached_use_jit_zygote_ = |
| props_->GetBool("persist.device_config.runtime_native_boot.profilebootclasspath", |
| "dalvik.vm.profilebootclasspath", |
| /*default_value=*/false); |
| } |
| |
| return cached_use_jit_zygote_.value(); |
| } |
| |
| const std::string& Artd::GetUserDefinedBootImageLocations() { |
| std::lock_guard<std::mutex> lock(cache_mu_); |
| return GetUserDefinedBootImageLocationsLocked(); |
| } |
| |
| const std::string& Artd::GetUserDefinedBootImageLocationsLocked() { |
| if (!cached_user_defined_boot_image_locations_.has_value()) { |
| cached_user_defined_boot_image_locations_ = props_->GetOrEmpty("dalvik.vm.boot-image"); |
| } |
| |
| return cached_user_defined_boot_image_locations_.value(); |
| } |
| |
| bool Artd::DenyArtApexDataFiles() { |
| std::lock_guard<std::mutex> lock(cache_mu_); |
| return DenyArtApexDataFilesLocked(); |
| } |
| |
| bool Artd::DenyArtApexDataFilesLocked() { |
| if (!cached_deny_art_apex_data_files_.has_value()) { |
| cached_deny_art_apex_data_files_ = |
| !props_->GetBool("odsign.verification.success", /*default_value=*/false); |
| } |
| |
| return cached_deny_art_apex_data_files_.value(); |
| } |
| |
| Result<std::string> Artd::GetProfman() { return BuildArtBinPath("profman"); } |
| |
| Result<CmdlineBuilder> Artd::GetArtExecCmdlineBuilder() { |
| std::string art_exec_path = OR_RETURN(BuildArtBinPath("art_exec")); |
| if (options_.is_pre_reboot) { |
| // "/mnt/compat_env" is prepared by dexopt_chroot_setup on Android V. |
| std::string compat_art_exec_path = "/mnt/compat_env" + art_exec_path; |
| if (OS::FileExists(compat_art_exec_path.c_str())) { |
| art_exec_path = std::move(compat_art_exec_path); |
| } |
| } |
| |
| CmdlineBuilder args; |
| args.Add(art_exec_path) |
| .Add("--drop-capabilities") |
| .AddIf(options_.is_pre_reboot, "--process-name-suffix=Pre-reboot Dexopt chroot"); |
| return args; |
| } |
| |
| bool Artd::ShouldUseDex2Oat64() { |
| return !props_->GetOrEmpty("ro.product.cpu.abilist64").empty() && |
| props_->GetBool("dalvik.vm.dex2oat64.enabled", /*default_value=*/false); |
| } |
| |
| bool Artd::ShouldUseDebugBinaries() { |
| return props_->GetOrEmpty("persist.sys.dalvik.vm.lib.2") == "libartd.so"; |
| } |
| |
| Result<std::string> Artd::GetDex2Oat() { |
| std::string binary_name = ShouldUseDebugBinaries() ? |
| (ShouldUseDex2Oat64() ? "dex2oatd64" : "dex2oatd32") : |
| (ShouldUseDex2Oat64() ? "dex2oat64" : "dex2oat32"); |
| return BuildArtBinPath(binary_name); |
| } |
| |
| bool Artd::ShouldCreateSwapFileForDexopt() { |
| // Create a swap file by default. Dex2oat will decide whether to use it or not. |
| return props_->GetBool("dalvik.vm.dex2oat-swap", /*default_value=*/true); |
| } |
| |
| void Artd::AddBootImageFlags(/*out*/ CmdlineBuilder& args) { |
| if (UseJitZygote()) { |
| args.Add("--force-jit-zygote"); |
| } else { |
| args.AddIfNonEmpty("--boot-image=%s", GetUserDefinedBootImageLocations()); |
| } |
| } |
| |
| void Artd::AddCompilerConfigFlags(const std::string& instruction_set, |
| const std::string& compiler_filter, |
| PriorityClass priority_class, |
| const DexoptOptions& dexopt_options, |
| /*out*/ CmdlineBuilder& args) { |
| args.Add("--instruction-set=%s", instruction_set); |
| std::string features_prop = ART_FORMAT("dalvik.vm.isa.{}.features", instruction_set); |
| args.AddIfNonEmpty("--instruction-set-features=%s", props_->GetOrEmpty(features_prop)); |
| std::string variant_prop = ART_FORMAT("dalvik.vm.isa.{}.variant", instruction_set); |
| args.AddIfNonEmpty("--instruction-set-variant=%s", props_->GetOrEmpty(variant_prop)); |
| |
| args.Add("--compiler-filter=%s", compiler_filter) |
| .Add("--compilation-reason=%s", dexopt_options.compilationReason); |
| |
| args.AddIf(priority_class >= PriorityClass::INTERACTIVE, "--compact-dex-level=none"); |
| |
| args.AddIfNonEmpty("--max-image-block-size=%s", |
| props_->GetOrEmpty("dalvik.vm.dex2oat-max-image-block-size")) |
| .AddIfNonEmpty("--very-large-app-threshold=%s", |
| props_->GetOrEmpty("dalvik.vm.dex2oat-very-large")) |
| .AddIfNonEmpty("--resolve-startup-const-strings=%s", |
| props_->GetOrEmpty("dalvik.vm.dex2oat-resolve-startup-strings")); |
| |
| args.AddIf(dexopt_options.debuggable, "--debuggable") |
| .AddIf(props_->GetBool("debug.generate-debug-info", /*default_value=*/false), |
| "--generate-debug-info") |
| .AddIf(props_->GetBool("dalvik.vm.dex2oat-minidebuginfo", /*default_value=*/false), |
| "--generate-mini-debug-info"); |
| |
| args.AddRuntimeIf(DenyArtApexDataFiles(), "-Xdeny-art-apex-data-files") |
| .AddRuntime("-Xtarget-sdk-version:%d", dexopt_options.targetSdkVersion) |
| .AddRuntimeIf(dexopt_options.hiddenApiPolicyEnabled, "-Xhidden-api-policy:enabled"); |
| } |
| |
| void Artd::AddPerfConfigFlags(PriorityClass priority_class, |
| /*out*/ CmdlineBuilder& art_exec_args, |
| /*out*/ CmdlineBuilder& dex2oat_args) { |
| // CPU set and number of threads. |
| std::string default_cpu_set_prop = "dalvik.vm.dex2oat-cpu-set"; |
| std::string default_threads_prop = "dalvik.vm.dex2oat-threads"; |
| std::string cpu_set; |
| std::string threads; |
| if (priority_class >= PriorityClass::BOOT) { |
| cpu_set = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-cpu-set"); |
| threads = props_->GetOrEmpty("dalvik.vm.boot-dex2oat-threads"); |
| } else if (priority_class >= PriorityClass::INTERACTIVE_FAST) { |
| cpu_set = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-cpu-set", default_cpu_set_prop); |
| threads = props_->GetOrEmpty("dalvik.vm.restore-dex2oat-threads", default_threads_prop); |
| } else if (priority_class <= PriorityClass::BACKGROUND) { |
| cpu_set = props_->GetOrEmpty("dalvik.vm.background-dex2oat-cpu-set", default_cpu_set_prop); |
| threads = props_->GetOrEmpty("dalvik.vm.background-dex2oat-threads", default_threads_prop); |
| } else { |
| cpu_set = props_->GetOrEmpty(default_cpu_set_prop); |
| threads = props_->GetOrEmpty(default_threads_prop); |
| } |
| dex2oat_args.AddIfNonEmpty("--cpu-set=%s", cpu_set).AddIfNonEmpty("-j%s", threads); |
| |
| if (priority_class < PriorityClass::BOOT) { |
| art_exec_args |
| .Add(priority_class <= PriorityClass::BACKGROUND ? "--set-task-profile=Dex2OatBackground" : |
| "--set-task-profile=Dex2OatBootComplete") |
| .Add("--set-priority=background"); |
| } |
| |
| dex2oat_args.AddRuntimeIfNonEmpty("-Xms%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xms")) |
| .AddRuntimeIfNonEmpty("-Xmx%s", props_->GetOrEmpty("dalvik.vm.dex2oat-Xmx")); |
| |
| // Enable compiling dex files in isolation on low ram devices. |
| // It takes longer but reduces the memory footprint. |
| dex2oat_args.AddIf(props_->GetBool("ro.config.low_ram", /*default_value=*/false), |
| "--compile-individually"); |
| |
| for (const std::string& flag : |
| Tokenize(props_->GetOrEmpty("dalvik.vm.dex2oat-flags"), /*delimiters=*/" ")) { |
| dex2oat_args.AddIfNonEmpty("%s", flag); |
| } |
| } |
| |
| Result<int> Artd::ExecAndReturnCode(const std::vector<std::string>& args, |
| int timeout_sec, |
| const ExecCallbacks& callbacks, |
| ProcessStat* stat) const { |
| std::string error_msg; |
| // Create a new process group so that we can kill the process subtree at once by killing the |
| // process group. |
| ExecResult result = exec_utils_->ExecAndReturnResult( |
| args, timeout_sec, callbacks, /*new_process_group=*/true, stat, &error_msg); |
| if (result.status != ExecResult::kExited) { |
| return Error() << error_msg; |
| } |
| return result.exit_code; |
| } |
| |
| Result<struct stat> Artd::Fstat(const File& file) const { |
| struct stat st; |
| if (fstat_(file.Fd(), &st) != 0) { |
| return Errorf("Unable to fstat file '{}'", file.GetPath()); |
| } |
| return st; |
| } |
| |
| Result<void> Artd::BindMountNewDir(const std::string& source, const std::string& target) const { |
| OR_RETURN(CreateDir(source)); |
| OR_RETURN(BindMount(source, target)); |
| OR_RETURN(restorecon_(target, /*se_context=*/std::nullopt, /*recurse=*/false)); |
| return {}; |
| } |
| |
| Result<void> Artd::BindMount(const std::string& source, const std::string& target) const { |
| if (mount_(source.c_str(), |
| target.c_str(), |
| /*fs_type=*/nullptr, |
| MS_BIND | MS_PRIVATE, |
| /*data=*/nullptr) != 0) { |
| return ErrnoErrorf("Failed to bind-mount '{}' at '{}'", source, target); |
| } |
| return {}; |
| } |
| |
| ScopedAStatus Artd::preRebootInit( |
| const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal, bool* _aidl_return) { |
| RETURN_FATAL_IF_NOT_PRE_REBOOT(options_); |
| |
| std::string tmp_dir = pre_reboot_tmp_dir_.value_or(kDefaultPreRebootTmpDir); |
| std::string preparation_done_file = tmp_dir + "/preparation_done"; |
| std::string classpath_file = tmp_dir + "/classpath.txt"; |
| std::string art_apex_data_dir = tmp_dir + "/art_apex_data"; |
| std::string odrefresh_dir = tmp_dir + "/odrefresh"; |
| |
| bool preparation_done = OS::FileExists(preparation_done_file.c_str()); |
| |
| if (!preparation_done) { |
| std::error_code ec; |
| bool is_empty = std::filesystem::is_empty(tmp_dir, ec); |
| if (ec) { |
| return NonFatal(ART_FORMAT("Failed to check dir '{}': {}", tmp_dir, ec.message())); |
| } |
| if (!is_empty) { |
| return Fatal( |
| "preRebootInit must not be concurrently called or retried after cancellation or failure"); |
| } |
| } |
| |
| OR_RETURN_NON_FATAL(PreRebootInitClearEnvs()); |
| OR_RETURN_NON_FATAL( |
| PreRebootInitSetEnvFromFile(init_environ_rc_path_.value_or("/init.environ.rc"))); |
| if (!preparation_done) { |
| OR_RETURN_NON_FATAL(PreRebootInitDeriveClasspath(classpath_file)); |
| } |
| OR_RETURN_NON_FATAL(PreRebootInitSetEnvFromFile(classpath_file)); |
| if (!preparation_done) { |
| OR_RETURN_NON_FATAL(BindMountNewDir(art_apex_data_dir, GetArtApexData())); |
| OR_RETURN_NON_FATAL(BindMountNewDir(odrefresh_dir, "/data/misc/odrefresh")); |
| ArtdCancellationSignal* cancellation_signal = |
| OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get())); |
| if (!OR_RETURN_NON_FATAL(PreRebootInitBootImages(cancellation_signal))) { |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| } |
| |
| if (!preparation_done) { |
| if (!WriteStringToFile(/*content=*/"", preparation_done_file)) { |
| return NonFatal( |
| ART_FORMAT("Failed to write '{}': {}", preparation_done_file, strerror(errno))); |
| } |
| } |
| |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| |
| Result<void> Artd::PreRebootInitClearEnvs() { |
| if (clearenv() != 0) { |
| return ErrnoErrorf("Failed to clear environment variables"); |
| } |
| return {}; |
| } |
| |
| Result<void> Artd::PreRebootInitSetEnvFromFile(const std::string& path) { |
| std::regex export_line_pattern("\\s*export\\s+(.+?)\\s+(.+)"); |
| |
| std::string content; |
| if (!ReadFileToString(path, &content)) { |
| return ErrnoErrorf("Failed to read '{}'", path); |
| } |
| bool found = false; |
| for (const std::string& line : Split(content, "\n")) { |
| if (line.find_first_of("\\\"") != std::string::npos) { |
| return Errorf("Backslashes and quotes in env var file are not supported for now, got '{}'", |
| line); |
| } |
| std::smatch match; |
| if (!std::regex_match(line, match, export_line_pattern)) { |
| continue; |
| } |
| const std::string& key = match[1].str(); |
| const std::string& value = match[2].str(); |
| LOG(INFO) << ART_FORMAT("Setting environment variable '{}' to '{}'", key, value); |
| if (setenv(key.c_str(), value.c_str(), /*replace=*/1) != 0) { |
| return ErrnoErrorf("Failed to set environment variable '{}' to '{}'", key, value); |
| } |
| found = true; |
| } |
| if (!found) { |
| return Errorf("Malformed env var file '{}': {}", path, content); |
| } |
| return {}; |
| } |
| |
| Result<void> Artd::PreRebootInitDeriveClasspath(const std::string& path) { |
| std::unique_ptr<File> output(OS::CreateEmptyFile(path.c_str())); |
| if (output == nullptr) { |
| return ErrnoErrorf("Failed to create '{}'", path); |
| } |
| |
| if (pre_reboot_build_props_ == nullptr) { |
| pre_reboot_build_props_ = std::make_unique<BuildSystemProperties>( |
| OR_RETURN(BuildSystemProperties::Create("/system/build.prop"))); |
| } |
| std::string sdk_version = pre_reboot_build_props_->GetOrEmpty("ro.build.version.sdk"); |
| std::string codename = pre_reboot_build_props_->GetOrEmpty("ro.build.version.codename"); |
| std::string known_codenames = |
| pre_reboot_build_props_->GetOrEmpty("ro.build.version.known_codenames"); |
| if (sdk_version.empty() || codename.empty() || known_codenames.empty()) { |
| return Errorf("Failed to read system properties"); |
| } |
| |
| CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder()); |
| args.Add("--keep-fds=%d", output->Fd()) |
| .Add("--") |
| .Add("/apex/com.android.sdkext/bin/derive_classpath") |
| .Add("--override-device-sdk-version=%s", sdk_version) |
| .Add("--override-device-codename=%s", codename) |
| .Add("--override-device-known-codenames=%s", known_codenames) |
| .Add("/proc/self/fd/%d", output->Fd()); |
| |
| LOG(INFO) << "Running derive_classpath: " << Join(args.Get(), /*separator=*/" "); |
| |
| Result<int> result = ExecAndReturnCode(args.Get(), kShortTimeoutSec); |
| if (!result.ok()) { |
| return Errorf("Failed to run derive_classpath: {}", result.error().message()); |
| } |
| |
| LOG(INFO) << ART_FORMAT("derive_classpath returned code {}", result.value()); |
| |
| if (result.value() != 0) { |
| return Errorf("derive_classpath returned an unexpected code: {}", result.value()); |
| } |
| |
| if (output->FlushClose() != 0) { |
| return ErrnoErrorf("Failed to flush and close '{}'", path); |
| } |
| |
| return {}; |
| } |
| |
| Result<bool> Artd::PreRebootInitBootImages(ArtdCancellationSignal* cancellation_signal) { |
| CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder()); |
| args.Add("--") |
| .Add(OR_RETURN(BuildArtBinPath("odrefresh"))) |
| .Add("--only-boot-images") |
| .Add("--compile"); |
| |
| LOG(INFO) << "Running odrefresh: " << Join(args.Get(), /*separator=*/" "); |
| |
| Result<int> result = |
| ExecAndReturnCode(args.Get(), kLongTimeoutSec, cancellation_signal->CreateExecCallbacks()); |
| if (!result.ok()) { |
| if (cancellation_signal->IsCancelled()) { |
| return false; |
| } |
| return Errorf("Failed to run odrefresh: {}", result.error().message()); |
| } |
| |
| LOG(INFO) << ART_FORMAT("odrefresh returned code {}", result.value()); |
| |
| if (result.value() != odrefresh::ExitCode::kCompilationSuccess && |
| result.value() != odrefresh::ExitCode::kOkay) { |
| return Errorf("odrefresh returned an unexpected code: {}", result.value()); |
| } |
| |
| return true; |
| } |
| |
| ScopedAStatus Artd::validateDexPath(const std::string& in_dexFile, |
| std::optional<std::string>* _aidl_return) { |
| RETURN_FATAL_IF_NOT_PRE_REBOOT(options_); |
| if (Result<void> result = ValidateDexPath(in_dexFile); !result.ok()) { |
| *_aidl_return = result.error().message(); |
| } else { |
| *_aidl_return = std::nullopt; |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus Artd::validateClassLoaderContext(const std::string& in_dexFile, |
| const std::string& in_classLoaderContext, |
| std::optional<std::string>* _aidl_return) { |
| RETURN_FATAL_IF_NOT_PRE_REBOOT(options_); |
| if (Result<void> result = ValidateClassLoaderContext(in_dexFile, in_classLoaderContext); |
| !result.ok()) { |
| *_aidl_return = result.error().message(); |
| } else { |
| *_aidl_return = std::nullopt; |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| Result<BuildSystemProperties> BuildSystemProperties::Create(const std::string& filename) { |
| std::string content; |
| if (!ReadFileToString(filename, &content)) { |
| return ErrnoErrorf("Failed to read '{}'", filename); |
| } |
| std::regex import_pattern(R"re(import\s.*)re"); |
| std::unordered_map<std::string, std::string> system_properties; |
| for (const std::string& raw_line : Split(content, "\n")) { |
| std::string line = Trim(raw_line); |
| if (line.empty() || line.starts_with('#') || std::regex_match(line, import_pattern)) { |
| continue; |
| } |
| size_t pos = line.find('='); |
| if (pos == std::string::npos || pos == 0 || (pos == 1 && line[1] == '?')) { |
| return Errorf("Malformed system property line '{}' in file '{}'", line, filename); |
| } |
| if (line[pos - 1] == '?') { |
| std::string key = line.substr(/*pos=*/0, /*n=*/pos - 1); |
| if (system_properties.find(key) == system_properties.end()) { |
| system_properties[key] = line.substr(pos + 1); |
| } |
| } else { |
| system_properties[line.substr(/*pos=*/0, /*n=*/pos)] = line.substr(pos + 1); |
| } |
| } |
| return BuildSystemProperties(std::move(system_properties)); |
| } |
| |
| std::string BuildSystemProperties::GetProperty(const std::string& key) const { |
| auto it = system_properties_.find(key); |
| return it != system_properties_.end() ? it->second : ""; |
| } |
| |
| } // namespace artd |
| } // namespace art |