diff options
author | 2021-04-07 10:43:01 +0100 | |
---|---|---|
committer | 2021-04-28 07:34:37 +0000 | |
commit | f96c9163a7dc68d750234b66cbada0b2c70c28bf (patch) | |
tree | d6c8f98643e2280f7c52b534d7e00ca299e3d0eb | |
parent | c182a17abb1857b6edaa7fb1fcda00595051775d (diff) |
odrefresh: move file-system utilities into odr_fs_utils.{h,cc}
Preparation for metrics support which will use the same functionality.
Add tests for odrefresh file-system utilities too.
Bug: 160683548
Test: atest art_odrefresh_tests
(cherry picked from commit dda82d2ed4d073866f3c3da3d88ad178261c62f4)
Merged-In: I1da2f477f51b1d445bbee3b22a9c1fb268d25e80
Change-Id: If4b8cfdb9c68167aae07268b29140e1162af2293
-rw-r--r-- | odrefresh/Android.bp | 3 | ||||
-rw-r--r-- | odrefresh/odr_config.h | 2 | ||||
-rw-r--r-- | odrefresh/odr_fs_utils.cc | 139 | ||||
-rw-r--r-- | odrefresh/odr_fs_utils.h | 47 | ||||
-rw-r--r-- | odrefresh/odr_fs_utils_test.cc | 167 | ||||
-rw-r--r-- | odrefresh/odrefresh.cc | 101 |
6 files changed, 364 insertions, 95 deletions
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp index 8e39edfe19..6f84e8f684 100644 --- a/odrefresh/Android.bp +++ b/odrefresh/Android.bp @@ -29,6 +29,7 @@ cc_defaults { defaults: ["art_defaults"], srcs: [ "odrefresh.cc", + "odr_fs_utils.cc", ], local_include_dirs: ["include"], header_libs: ["dexoptanalyzer_headers"], @@ -128,6 +129,8 @@ art_cc_test { header_libs: ["odrefresh_headers"], srcs: [ "odr_artifacts_test.cc", + "odr_fs_utils.cc", + "odr_fs_utils_test.cc", "odrefresh_test.cc", ], shared_libs: [ diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h index 6b0f212ca0..41619442c6 100644 --- a/odrefresh/odr_config.h +++ b/odrefresh/odr_config.h @@ -36,7 +36,7 @@ enum class ZygoteKind : uint8_t { kZygote32_64 = 1, // 64-bit primary zygote, 32-bit secondary zygote. kZygote64_32 = 2, - // 64-bit praimry zygote, no secondary zygote. + // 64-bit primary zygote, no secondary zygote. kZygote64 = 3 }; diff --git a/odrefresh/odr_fs_utils.cc b/odrefresh/odr_fs_utils.cc new file mode 100644 index 0000000000..9f3a68bda8 --- /dev/null +++ b/odrefresh/odr_fs_utils.cc @@ -0,0 +1,139 @@ +/* + * 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 "odr_fs_utils.h" + +#include <dirent.h> +#include <ftw.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <unistd.h> + +#include <iosfwd> +#include <memory> +#include <ostream> +#include <queue> +#include <string> +#include <string_view> +#include <type_traits> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/macros.h> +#include <android-base/strings.h> +#include <base/os.h> + +namespace art { +namespace odrefresh { + +// Callback for use with nftw(3) to assist with clearing files and sub-directories. +// This method removes files and directories below the top-level directory passed to nftw(). +static int NftwCleanUpCallback(const char* fpath, + const struct stat* sb ATTRIBUTE_UNUSED, + int typeflag, + struct FTW* ftwbuf) { + switch (typeflag) { + case FTW_F: + return unlink(fpath); + case FTW_DP: + return (ftwbuf->level == 0) ? 0 : rmdir(fpath); + default: + return -1; + } +} + +WARN_UNUSED bool CleanDirectory(const std::string& dir_path) { + if (!OS::DirectoryExists(dir_path.c_str())) { + return true; + } + + static constexpr int kMaxDescriptors = 4; // Limit the need for nftw() to re-open descriptors. + if (nftw(dir_path.c_str(), NftwCleanUpCallback, kMaxDescriptors, FTW_DEPTH | FTW_MOUNT) != 0) { + LOG(ERROR) << "Failed to clean-up '" << dir_path << "'"; + return false; + } + return true; +} + +WARN_UNUSED bool EnsureDirectoryExists(const std::string& absolute_path) { + if (absolute_path.empty() || absolute_path[0] != '/') { + LOG(ERROR) << "Path not absolute '" << absolute_path << "'"; + return false; + } + std::string path; + for (const std::string& directory : android::base::Split(absolute_path, "/")) { + path.append("/").append(directory); + if (!OS::DirectoryExists(path.c_str())) { + static constexpr mode_t kDirectoryMode = S_IRWXU | S_IRGRP | S_IXGRP| S_IROTH | S_IXOTH; + if (mkdir(path.c_str(), kDirectoryMode) != 0) { + PLOG(ERROR) << "Could not create directory: " << path; + return false; + } + } + } + return true; +} + +bool GetFreeSpace(const std::string& path, uint64_t* bytes) { + struct statvfs sv; + if (statvfs(path.c_str(), &sv) != 0) { + PLOG(ERROR) << "statvfs '" << path << "'"; + return false; + } + *bytes = sv.f_bfree * sv.f_bsize; + return true; +} + +bool GetUsedSpace(const std::string& path, uint64_t* bytes) { + static constexpr std::string_view kCurrentDirectory{"."}; + static constexpr std::string_view kParentDirectory{".."}; + static constexpr size_t kBytesPerBlock = 512; // see manual page for stat(2). + + uint64_t file_bytes = 0; + std::queue<std::string> unvisited; + unvisited.push(path); + while (!unvisited.empty()) { + std::string current = unvisited.front(); + unvisited.pop(); + std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(current.c_str()), closedir); + if (!dir) { + continue; + } + for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) { + std::string_view name{entity->d_name}; + if (name == kCurrentDirectory || name == kParentDirectory) { + continue; + } + std::string entity_name = current + "/" + entity->d_name; + if (entity->d_type == DT_DIR) { + unvisited.push(entity_name.c_str()); + } else if (entity->d_type == DT_REG) { + struct stat sb; + if (stat(entity_name.c_str(), &sb) != 0) { + PLOG(ERROR) << "Failed to stat() file " << entity_name; + continue; + } + file_bytes += sb.st_blocks * kBytesPerBlock; + } + } + } + *bytes = file_bytes; + return true; +} + +} // namespace odrefresh +} // namespace art diff --git a/odrefresh/odr_fs_utils.h b/odrefresh/odr_fs_utils.h new file mode 100644 index 0000000000..cefa11e1f4 --- /dev/null +++ b/odrefresh/odr_fs_utils.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ART_ODREFRESH_ODR_FS_UTILS_H_ +#define ART_ODREFRESH_ODR_FS_UTILS_H_ + +#include <cstdint> +#include <iosfwd> + +#include <android-base/macros.h> + +namespace art { +namespace odrefresh { + +// Cleans directory by removing all files and sub-directories under `dir_path`. +// Returns true on success, false otherwise. +WARN_UNUSED bool CleanDirectory(const std::string& dir_path); + +// Create all directories on `absolute_dir_path`. +// Returns true on success, false otherwise. +WARN_UNUSED bool EnsureDirectoryExists(const std::string& absolute_dir_path); + +// Get free space for filesystem containing `path`. +// Returns true on success, false otherwise. +WARN_UNUSED bool GetFreeSpace(const std::string& path, uint64_t* bytes); + +// Gets space used under directory `dir_path`. +// Returns true on success, false otherwise. +WARN_UNUSED bool GetUsedSpace(const std::string& dir_path, uint64_t* bytes); + +} // namespace odrefresh +} // namespace art + +#endif // ART_ODREFRESH_ODR_FS_UTILS_H_ diff --git a/odrefresh/odr_fs_utils_test.cc b/odrefresh/odr_fs_utils_test.cc new file mode 100644 index 0000000000..2b3cfaa05c --- /dev/null +++ b/odrefresh/odr_fs_utils_test.cc @@ -0,0 +1,167 @@ +/* + * 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 <fcntl.h> +#include <string.h> +#include <sys/stat.h> + +#include <iosfwd> +#include <memory> +#include <string> +#include <vector> + +#include "android-base/stringprintf.h" +#include "base/bit_utils.h" +#include "base/common_art_test.h" +#include "base/os.h" +#include "base/unix_file/fd_file.h" +#include "odr_fs_utils.h" + +namespace art { +namespace odrefresh { + +class OdrFsUtilsTest : public CommonArtTest {}; +namespace { + +static bool CreateFile(const char* file_path, size_t bytes) { + std::unique_ptr<File> fp(OS::CreateEmptyFile(file_path)); + if (!fp) { + return false; + } + + std::vector<char> buffer(bytes, 0xa5); + if (!fp->WriteFully(buffer.data(), buffer.size())) { + fp->Erase(); + return false; + } + + if (fp->FlushClose() != 0) { + fp->Erase(); + return false; + } + + return true; +} + +} // namespace + +TEST_F(OdrFsUtilsTest, CleanDirectory) { + ScratchDir scratch_dir(/*keep_files=*/false); + + // Create some sub-directories and files + const std::string dir_paths[] = { + scratch_dir.GetPath() + "/a", + scratch_dir.GetPath() + "/b", + scratch_dir.GetPath() + "/b/c", + scratch_dir.GetPath() + "/d" + }; + for (const auto& dir_path : dir_paths) { + ASSERT_EQ(0, mkdir(dir_path.c_str(), S_IRWXU)); + } + + const std::string file_paths[] = { + scratch_dir.GetPath() + "/zero.txt", + scratch_dir.GetPath() + "/a/one.txt", + scratch_dir.GetPath() + "/b/two.txt", + scratch_dir.GetPath() + "/b/c/three.txt", + scratch_dir.GetPath() + "/b/c/four.txt", + }; + for (const auto& file_path : file_paths) { + ASSERT_TRUE(CreateFile(file_path.c_str(), 4096)); + } + + // Clean all files and sub-directories + ASSERT_TRUE(CleanDirectory(scratch_dir.GetPath())); + + // Check nothing we created remains. + for (const auto& dir_path : dir_paths) { + ASSERT_FALSE(OS::DirectoryExists(dir_path.c_str())); + } + + for (const auto& file_path : file_paths) { + ASSERT_FALSE(OS::FileExists(file_path.c_str(), true)); + } +} + +TEST_F(OdrFsUtilsTest, EnsureDirectoryExistsBadPath) { + // Pick a path where not even a root test runner can write. + ASSERT_FALSE(EnsureDirectoryExists("/proc/unlikely/to/be/writable")); +} + +TEST_F(OdrFsUtilsTest, EnsureDirectoryExistsEmptyPath) { + ASSERT_FALSE(EnsureDirectoryExists("")); +} + +TEST_F(OdrFsUtilsTest, EnsureDirectoryExistsRelativePath) { + ASSERT_FALSE(EnsureDirectoryExists("a/b/c")); +} + +TEST_F(OdrFsUtilsTest, EnsureDirectoryExistsSubDirs) { + ScratchDir scratch_dir(/*keep_files=*/false); + + const char* relative_sub_dirs[] = {"a", "b/c", "d/e/f/"}; + for (const char* relative_sub_dir : relative_sub_dirs) { + ASSERT_TRUE(EnsureDirectoryExists(scratch_dir.GetPath() + "/" + relative_sub_dir)); + } +} + +TEST_F(OdrFsUtilsTest, GetUsedSpace) { + static constexpr size_t kFirstFileBytes = 1; + static constexpr size_t kSecondFileBytes = 16111; + static constexpr size_t kBytesPerBlock = 512; + + ScratchDir scratch_dir(/*keep_files=*/false); + + const std::string first_file_path = scratch_dir.GetPath() + "/1.dat"; + ASSERT_TRUE(CreateFile(first_file_path.c_str(), kFirstFileBytes)); + + struct stat sb; + ASSERT_EQ(0, stat(first_file_path.c_str(), &sb)); + ASSERT_EQ(kFirstFileBytes, static_cast<decltype(kFirstFileBytes)>(sb.st_size)); + + uint64_t bytes_used = 0; + ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath().c_str(), &bytes_used)); + ASSERT_EQ(static_cast<uint64_t>(sb.st_blksize), bytes_used); + + const std::string second_file_path = scratch_dir.GetPath() + "/2.dat"; + ASSERT_TRUE(CreateFile(second_file_path.c_str(), kSecondFileBytes)); + + ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath().c_str(), &bytes_used)); + uint64_t expected_bytes_used = RoundUp(kFirstFileBytes, sb.st_blocks * kBytesPerBlock) + + RoundUp(kSecondFileBytes, sb.st_blocks * kBytesPerBlock); + ASSERT_EQ(expected_bytes_used, bytes_used); + + const std::string sub_dir_path = scratch_dir.GetPath() + "/sub"; + ASSERT_TRUE(EnsureDirectoryExists(sub_dir_path)); + for (size_t i = 1; i < 32768; i *= 17) { + const std::string path = android::base::StringPrintf("%s/%zu", sub_dir_path.c_str(), i); + ASSERT_TRUE(CreateFile(path.c_str(), i)); + expected_bytes_used += RoundUp(i, sb.st_blocks * kBytesPerBlock); + ASSERT_TRUE(GetUsedSpace(scratch_dir.GetPath().c_str(), &bytes_used)); + ASSERT_EQ(expected_bytes_used, bytes_used); + } +} + +TEST_F(OdrFsUtilsTest, GetUsedSpaceBadPath) { + ScratchDir scratch_dir(/*keep_files=*/false); + const std::string bad_path = scratch_dir.GetPath() + "/bad_path"; + uint64_t bytes_used = ~0ull; + ASSERT_TRUE(GetUsedSpace(bad_path, &bytes_used)); + ASSERT_EQ(0ull, bytes_used); +} + +} // namespace odrefresh +} // namespace art diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc index 974b3c6a02..24ef7d1474 100644 --- a/odrefresh/odrefresh.cc +++ b/odrefresh/odrefresh.cc @@ -16,16 +16,13 @@ #include "odrefresh/odrefresh.h" -#include <dirent.h> #include <errno.h> #include <fcntl.h> -#include <ftw.h> #include <inttypes.h> #include <limits.h> #include <stdio.h> #include <string.h> #include <sys/stat.h> -#include <sys/statvfs.h> #include <sysexits.h> #include <time.h> #include <unistd.h> @@ -41,7 +38,6 @@ #include <memory> #include <optional> #include <ostream> -#include <queue> #include <sstream> #include <string> #include <string_view> @@ -57,7 +53,6 @@ #include "android-base/strings.h" #include "android/log.h" #include "arch/instruction_set.h" -#include "base/bit_utils.h" #include "base/file_utils.h" #include "base/globals.h" #include "base/macros.h" @@ -74,6 +69,7 @@ #include "odr_artifacts.h" #include "odr_config.h" +#include "odr_fs_utils.h" namespace art { namespace odrefresh { @@ -149,23 +145,6 @@ static std::string QuotePath(std::string_view path) { return Concatenate({"'", path, "'"}); } -// Create all directory and all required parents. -static WARN_UNUSED bool EnsureDirectoryExists(const std::string& absolute_path) { - CHECK(absolute_path.size() > 0 && absolute_path[0] == '/'); - std::string path; - for (const std::string& directory : android::base::Split(absolute_path, "/")) { - path.append("/").append(directory); - if (!OS::DirectoryExists(path.c_str())) { - static constexpr mode_t kDirectoryMode = S_IRWXU | S_IRGRP | S_IXGRP| S_IROTH | S_IXOTH; - if (mkdir(path.c_str(), kDirectoryMode) != 0) { - PLOG(ERROR) << "Could not create directory: " << path; - return false; - } - } - } - return true; -} - static void EraseFiles(const std::vector<std::unique_ptr<File>>& files) { for (auto& file : files) { file->Erase(/*unlink=*/true); @@ -919,41 +898,6 @@ class OnDeviceRefresh final { return exit_code; } - static bool GetFreeSpace(const char* path, uint64_t* bytes) { - struct statvfs sv; - if (statvfs(path, &sv) != 0) { - PLOG(ERROR) << "statvfs '" << path << "'"; - return false; - } - *bytes = sv.f_bfree * sv.f_bsize; - return true; - } - - static bool GetUsedSpace(const char* path, uint64_t* bytes) { - *bytes = 0; - - std::queue<std::string> unvisited; - unvisited.push(path); - while (!unvisited.empty()) { - std::string current = unvisited.front(); - std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(current.c_str()), closedir); - for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) { - if (entity->d_name[0] == '.') { - continue; - } - std::string entity_name = Concatenate({current, "/", entity->d_name}); - if (entity->d_type == DT_DIR) { - unvisited.push(entity_name.c_str()); - } else if (entity->d_type == DT_REG) { - // RoundUp file size to number of blocks. - *bytes += RoundUp(OS::GetFileSizeBytes(entity_name.c_str()), 512); - } - } - unvisited.pop(); - } - return true; - } - static void ReportSpace() { uint64_t bytes; std::string data_dir = GetArtApexData(); @@ -965,44 +909,13 @@ class OnDeviceRefresh final { } } - // Callback for use with nftw(3) to assist with clearing files and sub-directories. - // This method removes files and directories below the top-level directory passed to nftw(). - static int NftwUnlinkRemoveCallback(const char* fpath, - const struct stat* sb ATTRIBUTE_UNUSED, - int typeflag, - struct FTW* ftwbuf) { - switch (typeflag) { - case FTW_F: - return unlink(fpath); - case FTW_DP: - return (ftwbuf->level == 0) ? 0 : rmdir(fpath); - default: - return -1; - } - } - - // Recursively remove files and directories under `top_dir`, but preserve `top_dir` itself. - // Returns true on success, false otherwise. - WARN_UNUSED bool RecursiveRemoveBelow(const char* top_dir) const { + WARN_UNUSED bool CleanApexdataDirectory() const { + const std::string& apex_data_path = GetArtApexData(); if (config_.GetDryRun()) { - LOG(INFO) << "Files under " << QuotePath(top_dir) << " would be removed (dry-run)."; - return true; - } - - if (!OS::DirectoryExists(top_dir)) { + LOG(INFO) << "Files under `" << QuotePath(apex_data_path) << " would be removed (dry-run)."; return true; } - - static constexpr int kMaxDescriptors = 4; // Limit the need for nftw() to re-open descriptors. - if (nftw(top_dir, NftwUnlinkRemoveCallback, kMaxDescriptors, FTW_DEPTH | FTW_MOUNT) != 0) { - LOG(ERROR) << "Failed to clean-up " << QuotePath(top_dir); - return false; - } - return true; - } - - WARN_UNUSED bool CleanApexdataDirectory() const { - return RecursiveRemoveBelow(GetArtApexData().c_str()); + return CleanDirectory(apex_data_path); } WARN_UNUSED bool RemoveArtifacts(const OdrArtifacts& artifacts) const { @@ -1299,7 +1212,7 @@ class OnDeviceRefresh final { if (!CompileBootExtensionArtifacts( isa, staging_dir, &dex2oat_invocation_count, &error_msg)) { LOG(ERROR) << "Compilation of BCP failed: " << error_msg; - if (!RecursiveRemoveBelow(staging_dir)) { + if (!config_.GetDryRun() && !CleanDirectory(staging_dir)) { return ExitCode::kCleanupFailed; } return ExitCode::kCompilationFailed; @@ -1310,7 +1223,7 @@ class OnDeviceRefresh final { if (force_compile || !SystemServerArtifactsExistOnData(&error_msg)) { if (!CompileSystemServerArtifacts(staging_dir, &dex2oat_invocation_count, &error_msg)) { LOG(ERROR) << "Compilation of system_server failed: " << error_msg; - if (!RecursiveRemoveBelow(staging_dir)) { + if (!config_.GetDryRun() && !CleanDirectory(staging_dir)) { return ExitCode::kCleanupFailed; } return ExitCode::kCompilationFailed; |