diff options
author | Nikita Ioffe <ioffe@google.com> | 2020-10-05 15:53:16 +0100 |
---|---|---|
committer | Nikita Ioffe <ioffe@google.com> | 2021-01-21 21:31:02 +0000 |
commit | 66bd61adb95077787c37bf4aa914cb242a52d413 (patch) | |
tree | b83253037c2515255867bf9e24cbbd49191a291f | |
parent | 22613b7917e90d3f75cbd107bc49f82895381388 (diff) |
Fallback to /data/apex/sessions if /metadata partition is absent
There might be devices that support updatable apex, but don't have
/metadata partition. For them we need to fallback to storing sessions on
/data/apex/sessions.
This change implements this logic by introducing GetSessionsDir static
method which will pick first existing directory between
/metadata/apex/sessions and /data/apex/sessions. The evaluation will
happen only once, all other calls to GetSessionsDir will return the
cached value.
Additionally, the logic for migrating sessions from /data/apex/sessions
to /metadata/apex/sessions is changed a little bit. Now apexd will
iterate over entires in /data/apex/sessions, and then recursively
copy their content to the corresponding directory under
/metadata/apex/sessions, and then delete the entry under
/data/apex/sessions. This way a directory created by init
(/data/apex/sessions) is kept, which makes it easier to write unit tests
for apexd.
Logic is implemented as function inside apexd_utils.h for the ease of
unit testing.
Test: atest ApexTestCases
Test: atest apexd_host_tests
Test: atest CtsStagedInstallHostTestCases
Bug: 169932155
Bug: 176314162
Merged-In: Ifea8840ee5f1e56428d80bbb3b4e5e078176a578
Change-Id: Ia5e31e53edb411a023c8b090b7eb53ae977adc3e
(cherry picked from commit 7ffc3f6bd74b0b5567e6425090e9d13b6f5d4172)
(cherry picked from commit 69e06a1ddf509e4fc0734e044655d2b876b2bf45)
-rw-r--r-- | apexd/Android.bp | 2 | ||||
-rw-r--r-- | apexd/apexd.cpp | 27 | ||||
-rw-r--r-- | apexd/apexd_main.cpp | 5 | ||||
-rw-r--r-- | apexd/apexd_session.cpp | 94 | ||||
-rw-r--r-- | apexd/apexd_session.h | 12 | ||||
-rw-r--r-- | apexd/apexd_session_test.cpp | 117 | ||||
-rw-r--r-- | apexd/apexd_utils.h | 71 | ||||
-rw-r--r-- | apexd/apexd_utils_test.cpp | 183 | ||||
-rw-r--r-- | apexd/apexservice_test.cpp | 39 | ||||
-rw-r--r-- | tests/src/com/android/tests/apex/ApexdHostTest.java | 28 |
10 files changed, 489 insertions, 89 deletions
diff --git a/apexd/Android.bp b/apexd/Android.bp index e8d221d..b2f8202 100644 --- a/apexd/Android.bp +++ b/apexd/Android.bp @@ -291,7 +291,9 @@ cc_test { "apex_database_test.cpp", "apex_file_test.cpp", "apex_manifest_test.cpp", + "apexd_session_test.cpp", "apexd_verity_test.cpp", + "apexd_utils_test.cpp", "apexservice_test.cpp", ], host_supported: false, diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp index 0010c42..c302c70 100644 --- a/apexd/apexd.cpp +++ b/apexd/apexd.cpp @@ -1320,30 +1320,7 @@ Result<void> restoreCeData(const int user_id, const int rollback_id, // Migrates sessions directory from /data/apex/sessions to // /metadata/apex/sessions, if necessary. Result<void> migrateSessionsDirIfNeeded() { - namespace fs = std::filesystem; - auto from_path = std::string(kApexDataDir) + "/sessions"; - auto exists = PathExists(from_path); - if (!exists) { - return Error() << "Failed to access " << from_path << ": " - << exists.error(); - } - if (!*exists) { - LOG(DEBUG) << from_path << " does not exist. Nothing to migrate."; - return {}; - } - auto to_path = kApexSessionsDir; - std::error_code error_code; - fs::copy(from_path, to_path, fs::copy_options::recursive, error_code); - if (error_code) { - return Error() << "Failed to copy old sessions directory" - << error_code.message(); - } - fs::remove_all(from_path, error_code); - if (error_code) { - return Error() << "Failed to delete old sessions directory " - << error_code.message(); - } - return {}; + return ApexSession::MigrateToMetadataSessionsDir(); } Result<void> destroySnapshots(const std::string& base_dir, @@ -1455,7 +1432,7 @@ void deleteDePreRestoreSnapshots(const ApexSession& session) { } void scanStagedSessionsDirAndStage() { - LOG(INFO) << "Scanning " << kApexSessionsDir + LOG(INFO) << "Scanning " << ApexSession::GetSessionsDir() << " looking for sessions to be activated."; auto sessionsToActivate = diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp index c60f4fa..d460150 100644 --- a/apexd/apexd_main.cpp +++ b/apexd/apexd_main.cpp @@ -133,7 +133,10 @@ int main(int /*argc*/, char** argv) { bool booting = android::apex::isBooting(); if (booting) { - android::apex::migrateSessionsDirIfNeeded(); + if (auto res = android::apex::migrateSessionsDirIfNeeded(); !res.ok()) { + LOG(ERROR) << "Failed to migrate sessions to /metadata partition : " + << res.error(); + } android::apex::onStart(); } android::apex::binder::CreateAndRegisterService(); diff --git a/apexd/apexd_session.cpp b/apexd/apexd_session.cpp index 3d916c4..b8dd87b 100644 --- a/apexd/apexd_session.cpp +++ b/apexd/apexd_session.cpp @@ -22,6 +22,7 @@ #include "session_state.pb.h" #include <android-base/logging.h> +#include <android-base/stringprintf.h> #include <dirent.h> #include <sys/stat.h> @@ -32,6 +33,7 @@ using android::base::Error; using android::base::Result; +using android::base::StringPrintf; using apex::proto::SessionState; namespace android { @@ -39,60 +41,49 @@ namespace apex { namespace { -static constexpr const char* kStateFileName = "state"; +// Starting from R, apexd prefers /metadata partition (kNewApexSessionsDir) as +// location for sessions-related information. For devices that don't have +// /metadata partition, apexd will fallback to the /data one +// (kOldApexSessionsDir). +static constexpr const char* kOldApexSessionsDir = "/data/apex/sessions"; +static constexpr const char* kNewApexSessionsDir = "/metadata/apex/sessions"; -std::string getSessionDir(int session_id) { - return kApexSessionsDir + "/" + std::to_string(session_id); -} +static constexpr const char* kStateFileName = "state"; -std::string getSessionStateFilePath(int session_id) { - return getSessionDir(session_id) + "/" + kStateFileName; -} +} // namespace -Result<std::string> createSessionDirIfNeeded(int session_id) { - // create /data/sessions - auto res = createDirIfNeeded(kApexSessionsDir, 0700); - if (!res.ok()) { - return res.error(); - } - // create /data/sessions/session_id - std::string sessionDir = getSessionDir(session_id); - res = createDirIfNeeded(sessionDir, 0700); - if (!res.ok()) { - return res.error(); - } +ApexSession::ApexSession(SessionState state) : state_(std::move(state)) {} - return sessionDir; +std::string ApexSession::GetSessionsDir() { + static std::string result; + static std::once_flag once_flag; + std::call_once(once_flag, [&]() { + auto status = + FindFirstExistingDirectory(kNewApexSessionsDir, kOldApexSessionsDir); + if (!status.ok()) { + LOG(FATAL) << status.error(); + } + result = std::move(*status); + }); + return result; } -Result<void> deleteSessionDir(int session_id) { - std::string session_dir = getSessionDir(session_id); - LOG(DEBUG) << "Deleting " << session_dir; - auto path = std::filesystem::path(session_dir); - std::error_code error_code; - std::filesystem::remove_all(path, error_code); - if (error_code) { - return Error() << "Failed to delete " << session_dir << " : " - << error_code.message(); - } - return {}; +Result<void> ApexSession::MigrateToMetadataSessionsDir() { + return MoveDir(kOldApexSessionsDir, kNewApexSessionsDir); } -} // namespace - -ApexSession::ApexSession(SessionState state) : state_(std::move(state)) {} - Result<ApexSession> ApexSession::CreateSession(int session_id) { SessionState state; // Create session directory - auto sessionPath = createSessionDirIfNeeded(session_id); - if (!sessionPath.ok()) { - return sessionPath.error(); + std::string session_dir = GetSessionsDir() + "/" + std::to_string(session_id); + if (auto status = createDirIfNeeded(session_dir, 0700); !status.ok()) { + return status.error(); } state.set_id(session_id); return ApexSession(state); } + Result<ApexSession> ApexSession::GetSessionFromFile(const std::string& path) { SessionState state; std::fstream stateFile(path, std::ios::in | std::ios::binary); @@ -108,7 +99,8 @@ Result<ApexSession> ApexSession::GetSessionFromFile(const std::string& path) { } Result<ApexSession> ApexSession::GetSession(int session_id) { - auto path = getSessionStateFilePath(session_id); + auto path = StringPrintf("%s/%d/%s", GetSessionsDir().c_str(), session_id, + kStateFileName); return GetSessionFromFile(path); } @@ -117,7 +109,7 @@ std::vector<ApexSession> ApexSession::GetSessions() { std::vector<ApexSession> sessions; Result<std::vector<std::string>> sessionPaths = ReadDir( - kApexSessionsDir, [](const std::filesystem::directory_entry& entry) { + GetSessionsDir(), [](const std::filesystem::directory_entry& entry) { std::error_code ec; return entry.is_directory(ec); }); @@ -238,19 +230,29 @@ Result<void> ApexSession::UpdateStateAndCommit( const SessionState::State& session_state) { state_.set_state(session_state); - auto stateFilePath = getSessionStateFilePath(state_.id()); + auto state_file_path = StringPrintf("%s/%d/%s", GetSessionsDir().c_str(), + state_.id(), kStateFileName); - std::fstream stateFile(stateFilePath, - std::ios::out | std::ios::trunc | std::ios::binary); - if (!state_.SerializeToOstream(&stateFile)) { - return Error() << "Failed to write state file " << stateFilePath; + std::fstream state_file(state_file_path, + std::ios::out | std::ios::trunc | std::ios::binary); + if (!state_.SerializeToOstream(&state_file)) { + return Error() << "Failed to write state file " << state_file_path; } return {}; } Result<void> ApexSession::DeleteSession() const { - return deleteSessionDir(GetId()); + std::string session_dir = GetSessionsDir() + "/" + std::to_string(GetId()); + LOG(INFO) << "Deleting " << session_dir; + auto path = std::filesystem::path(session_dir); + std::error_code error_code; + std::filesystem::remove_all(path, error_code); + if (error_code) { + return Error() << "Failed to delete " << session_dir << " : " + << error_code.message(); + } + return {}; } std::ostream& operator<<(std::ostream& out, const ApexSession& session) { diff --git a/apexd/apexd_session.h b/apexd/apexd_session.h index e0cea91..fa977ef 100644 --- a/apexd/apexd_session.h +++ b/apexd/apexd_session.h @@ -28,10 +28,18 @@ namespace android { namespace apex { -static const std::string kApexSessionsDir = "/metadata/apex/sessions"; - class ApexSession { public: + // Returns top-level directory to store sessions metadata in. + // If device has /metadata partition, this will return + // /metadata/apex/sessions, on all other devices it will return + // /data/apex/sessions. + static std::string GetSessionsDir(); + // Migrates content of /data/apex/sessions to /metadata/apex/sessions. + // If device doesn't have /metadata partition this call will be a no-op. + // If /data/apex/sessions this call will also be a no-op. + static android::base::Result<void> MigrateToMetadataSessionsDir(); + static android::base::Result<ApexSession> CreateSession(int session_id); static android::base::Result<ApexSession> GetSession(int session_id); static std::vector<ApexSession> GetSessions(); diff --git a/apexd/apexd_session_test.cpp b/apexd/apexd_session_test.cpp new file mode 100644 index 0000000..bec0561 --- /dev/null +++ b/apexd/apexd_session_test.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 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 <filesystem> +#include <fstream> +#include <string> + +#include <errno.h> + +#include <android-base/file.h> +#include <android-base/result.h> +#include <android-base/scopeguard.h> +#include <android-base/stringprintf.h> +#include <gtest/gtest.h> + +#include "apexd_session.h" +#include "apexd_test_utils.h" +#include "apexd_utils.h" +#include "session_state.pb.h" + +namespace android { +namespace apex { +namespace { + +using android::apex::testing::IsOk; +using android::base::make_scope_guard; + +// TODO(b/170329726): add unit tests for apexd_sessions.h + +TEST(ApexdSessionTest, GetSessionsDirSessionsStoredInMetadata) { + if (access("/metadata", F_OK) != 0) { + GTEST_SKIP() << "Device doesn't have /metadata partition"; + } + + std::string result = ApexSession::GetSessionsDir(); + ASSERT_EQ(result, "/metadata/apex/sessions"); +} + +TEST(ApexdSessionTest, GetSessionsDirNoMetadataPartitionFallbackToData) { + if (access("/metadata", F_OK) == 0) { + GTEST_SKIP() << "Device has /metadata partition"; + } + + std::string result = ApexSession::GetSessionsDir(); + ASSERT_EQ(result, "/data/apex/sessions"); +} + +TEST(ApexdSessionTest, MigrateToMetadataSessionsDir) { + namespace fs = std::filesystem; + + if (access("/metadata", F_OK) != 0) { + GTEST_SKIP() << "Device doesn't have /metadata partition"; + } + + // This is a very ugly test set up, but to have something better we need to + // refactor ApexSession class. + class TestApexSession { + public: + TestApexSession(int id, const SessionState::State& state) { + path_ = "/data/apex/sessions/" + std::to_string(id); + if (auto status = createDirIfNeeded(path_, 0700); !status.ok()) { + ADD_FAILURE() << "Failed to create " << path_ << " : " + << status.error(); + } + SessionState session; + session.set_id(id); + session.set_state(state); + std::fstream state_file( + path_ + "/state", std::ios::out | std::ios::trunc | std::ios::binary); + if (!session.SerializeToOstream(&state_file)) { + ADD_FAILURE() << "Failed to write to " << path_; + } + } + + ~TestApexSession() { fs::remove_all(path_); } + + private: + std::string path_; + }; + + auto deleter = make_scope_guard([&]() { + fs::remove_all("/metadata/apex/sessions/239"); + fs::remove_all("/metadata/apex/sessions/1543"); + }); + + TestApexSession session1(239, SessionState::SUCCESS); + TestApexSession session2(1543, SessionState::ACTIVATION_FAILED); + + ASSERT_TRUE(IsOk(ApexSession::MigrateToMetadataSessionsDir())); + + ASSERT_EQ(2u, ApexSession::GetSessions().size()); + + auto migrated_session_1 = ApexSession::GetSession(239); + ASSERT_TRUE(IsOk(migrated_session_1)); + ASSERT_EQ(SessionState::SUCCESS, migrated_session_1->GetState()); + + auto migrated_session_2 = ApexSession::GetSession(1543); + ASSERT_TRUE(IsOk(migrated_session_2)); + ASSERT_EQ(SessionState::ACTIVATION_FAILED, migrated_session_2->GetState()); +} + +} // namespace +} // namespace apex +} // namespace android diff --git a/apexd/apexd_utils.h b/apexd/apexd_utils.h index 2759f9a..6997efc 100644 --- a/apexd/apexd_utils.h +++ b/apexd/apexd_utils.h @@ -21,6 +21,7 @@ #include <filesystem> #include <string> #include <thread> +#include <type_traits> #include <vector> #include <dirent.h> @@ -245,6 +246,76 @@ inline Result<std::vector<std::string>> GetDeUserDirs() { return GetSubdirs(kDeNDataDir); } +// Returns first path between |first_dir| and |second_dir| that correspond to a +// existing directory. Returns error if neither |first_dir| nor |second_dir| +// correspond to an existing directory. +inline Result<std::string> FindFirstExistingDirectory( + const std::string& first_dir, const std::string& second_dir) { + struct stat stat_buf; + if (stat(first_dir.c_str(), &stat_buf) != 0) { + PLOG(WARNING) << "Failed to stat " << first_dir; + if (stat(second_dir.c_str(), &stat_buf) != 0) { + return ErrnoError() << "Failed to stat " << second_dir; + } + if (!S_ISDIR(stat_buf.st_mode)) { + return Error() << second_dir << " is not a directory"; + } + return second_dir; + } + + if (S_ISDIR(stat_buf.st_mode)) { + return first_dir; + } + LOG(WARNING) << first_dir << " is not a directory"; + + if (stat(second_dir.c_str(), &stat_buf) != 0) { + return ErrnoError() << "Failed to stat " << second_dir; + } + if (!S_ISDIR(stat_buf.st_mode)) { + return Error() << second_dir << " is not a directory"; + } + return second_dir; +} + +// Copies all entries under |from| directory to |to| directory, and then them. +// Leaving |from| empty. +inline Result<void> MoveDir(const std::string& from, const std::string& to) { + struct stat stat_buf; + if (stat(to.c_str(), &stat_buf) != 0) { + return ErrnoError() << "Failed to stat " << to; + } + if (!S_ISDIR(stat_buf.st_mode)) { + return Error() << to << " is not a directory"; + } + + namespace fs = std::filesystem; + std::error_code ec; + auto it = fs::directory_iterator(from, ec); + if (ec) { + return Error() << "Can't read " << from << " : " << ec.message(); + } + + for (const auto& end = fs::directory_iterator(); it != end;) { + auto from_path = it->path(); + it.increment(ec); + if (ec) { + return Error() << "Can't read " << from << " : " << ec.message(); + } + auto to_path = to / from_path.filename(); + fs::copy(from_path, to_path, fs::copy_options::recursive, ec); + if (ec) { + return Error() << "Failed to copy " << from_path << " to " << to_path + << " : " << ec.message(); + } + fs::remove_all(from_path, ec); + if (ec) { + return Error() << "Failed to delete " << from_path << " : " + << ec.message(); + } + } + return {}; +} + } // namespace apex } // namespace android diff --git a/apexd/apexd_utils_test.cpp b/apexd/apexd_utils_test.cpp new file mode 100644 index 0000000..06b9c46 --- /dev/null +++ b/apexd/apexd_utils_test.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2020 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 <filesystem> +#include <string> + +#include <errno.h> + +#include <android-base/file.h> +#include <android-base/result.h> +#include <android-base/stringprintf.h> +#include <gtest/gtest.h> + +#include "apexd_test_utils.h" +#include "apexd_utils.h" + +namespace android { +namespace apex { +namespace { + +using android::apex::testing::IsOk; +using android::base::Basename; +using android::base::StringPrintf; +using ::testing::UnorderedElementsAre; +using ::testing::UnorderedElementsAreArray; + +// TODO(b/170327382): add unit tests for apexd_utils.h + +TEST(ApexdUtilTest, FindFirstExistingDirectoryBothExist) { + TemporaryDir first_dir; + TemporaryDir second_dir; + auto result = FindFirstExistingDirectory(first_dir.path, second_dir.path); + ASSERT_TRUE(IsOk(result)); + ASSERT_EQ(*result, first_dir.path); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryOnlyFirstExist) { + TemporaryDir first_dir; + auto second_dir = "/data/local/tmp/does/not/exist"; + auto result = FindFirstExistingDirectory(first_dir.path, second_dir); + ASSERT_TRUE(IsOk(result)); + ASSERT_EQ(*result, first_dir.path); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryOnlySecondExist) { + auto first_dir = "/data/local/tmp/does/not/exist"; + TemporaryDir second_dir; + auto result = FindFirstExistingDirectory(first_dir, second_dir.path); + ASSERT_TRUE(IsOk(result)); + ASSERT_EQ(*result, second_dir.path); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryNoneExist) { + auto first_dir = "/data/local/tmp/does/not/exist"; + auto second_dir = "/data/local/tmp/also/does/not/exist"; + auto result = FindFirstExistingDirectory(first_dir, second_dir); + ASSERT_FALSE(IsOk(result)); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryFirstFileSecondDir) { + TemporaryFile first_file; + TemporaryDir second_dir; + auto result = FindFirstExistingDirectory(first_file.path, second_dir.path); + ASSERT_TRUE(IsOk(result)); + ASSERT_EQ(*result, second_dir.path); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryFirstDirSecondFile) { + TemporaryDir first_dir; + TemporaryFile second_file; + auto result = FindFirstExistingDirectory(first_dir.path, second_file.path); + ASSERT_TRUE(IsOk(result)); + ASSERT_EQ(*result, first_dir.path); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryBothFiles) { + TemporaryFile first_file; + TemporaryFile second_file; + auto result = FindFirstExistingDirectory(first_file.path, second_file.path); + ASSERT_FALSE(IsOk(result)); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryFirstFileSecondDoesNotExist) { + TemporaryFile first_file; + auto second_dir = "/data/local/tmp/does/not/exist"; + auto result = FindFirstExistingDirectory(first_file.path, second_dir); + ASSERT_FALSE(IsOk(result)); +} + +TEST(ApexdUtilTest, FindFirstExistingDirectoryFirsDoesNotExistSecondFile) { + auto first_dir = "/data/local/tmp/does/not/exist"; + TemporaryFile second_file; + auto result = FindFirstExistingDirectory(first_dir, second_file.path); + ASSERT_FALSE(IsOk(result)); +} + +TEST(ApexdUtilTest, MoveDir) { + namespace fs = std::filesystem; + + TemporaryDir from; + TemporaryDir to; + + TemporaryFile from_1(from.path); + auto from_subdir = StringPrintf("%s/subdir", from.path); + if (mkdir(from_subdir.c_str(), 07000) != 0) { + FAIL() << "Failed to mkdir " << from_subdir << " : " << strerror(errno); + } + TemporaryFile from_2(from_subdir); + + auto result = MoveDir(from.path, to.path); + ASSERT_TRUE(IsOk(result)); + ASSERT_TRUE(fs::is_empty(from.path)); + + std::vector<std::string> content; + for (const auto& it : fs::recursive_directory_iterator(to.path)) { + content.push_back(it.path()); + } + + static const std::vector<std::string> expected = { + StringPrintf("%s/%s", to.path, Basename(from_1.path).c_str()), + StringPrintf("%s/subdir", to.path), + StringPrintf("%s/subdir/%s", to.path, Basename(from_2.path).c_str()), + }; + ASSERT_THAT(content, UnorderedElementsAreArray(expected)); +} + +TEST(ApexdUtilTest, MoveDirFromIsNotDirectory) { + TemporaryFile from; + TemporaryDir to; + ASSERT_FALSE(IsOk(MoveDir(from.path, to.path))); +} + +TEST(ApexdUtilTest, MoveDirToIsNotDirectory) { + TemporaryDir from; + TemporaryFile to; + TemporaryFile from_1(from.path); + ASSERT_FALSE(IsOk(MoveDir(from.path, to.path))); +} + +TEST(ApexdUtilTest, MoveDirFromDoesNotExist) { + TemporaryDir to; + ASSERT_FALSE(IsOk(MoveDir("/data/local/tmp/does/not/exist", to.path))); +} + +TEST(ApexdUtilTest, MoveDirToDoesNotExist) { + namespace fs = std::filesystem; + + TemporaryDir from; + TemporaryFile from_1(from.path); + auto from_subdir = StringPrintf("%s/subdir", from.path); + if (mkdir(from_subdir.c_str(), 07000) != 0) { + FAIL() << "Failed to mkdir " << from_subdir << " : " << strerror(errno); + } + TemporaryFile from_2(from_subdir); + + ASSERT_FALSE(IsOk(MoveDir(from.path, "/data/local/tmp/does/not/exist"))); + + // Check that |from| directory is not empty. + std::vector<std::string> content; + for (const auto& it : fs::recursive_directory_iterator(from.path)) { + content.push_back(it.path()); + } + + ASSERT_THAT(content, + UnorderedElementsAre(from_1.path, from_subdir, from_2.path)); +} + +} // namespace +} // namespace apex +} // namespace android diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp index 4c054f1..024cf50 100644 --- a/apexd/apexservice_test.cpp +++ b/apexd/apexservice_test.cpp @@ -94,6 +94,26 @@ using MountedApexData = MountedApexDatabase::MountedApexData; namespace fs = std::filesystem; +static void CleanDir(const std::string& dir) { + if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) { + LOG(WARNING) << dir << " does not exist"; + return; + } + auto status = WalkDir(dir, [](const fs::directory_entry& p) { + std::error_code ec; + fs::file_status status = p.status(ec); + ASSERT_FALSE(ec) << "Failed to stat " << p.path() << " : " << ec.message(); + if (fs::is_directory(status)) { + fs::remove_all(p.path(), ec); + } else { + fs::remove(p.path(), ec); + } + ASSERT_FALSE(ec) << "Failed to delete " << p.path() << " : " + << ec.message(); + }); + ASSERT_TRUE(IsOk(status)); +} + class ApexServiceTest : public ::testing::Test { public: ApexServiceTest() { @@ -447,21 +467,10 @@ class ApexServiceTest : public ::testing::Test { private: void CleanUp() { - auto status = WalkDir(kApexDataDir, [](const fs::directory_entry& p) { - std::error_code ec; - fs::file_status status = p.status(ec); - ASSERT_FALSE(ec) << "Failed to stat " << p.path() << " : " - << ec.message(); - if (fs::is_directory(status)) { - fs::remove_all(p.path(), ec); - } else { - fs::remove(p.path(), ec); - } - ASSERT_FALSE(ec) << "Failed to delete " << p.path() << " : " - << ec.message(); - }); - fs::remove_all(kApexSessionsDir); - ASSERT_TRUE(IsOk(status)); + CleanDir(kActiveApexPackagesDataDir); + CleanDir(kApexBackupDir); + CleanDir(kApexHashTreeDir); + CleanDir(ApexSession::GetSessionsDir()); DeleteIfExists("/data/misc_ce/0/apexdata/apex.apexd_test"); DeleteIfExists("/data/misc_ce/0/apexrollback/123456"); diff --git a/tests/src/com/android/tests/apex/ApexdHostTest.java b/tests/src/com/android/tests/apex/ApexdHostTest.java index fc1fa4b..ac18357 100644 --- a/tests/src/com/android/tests/apex/ApexdHostTest.java +++ b/tests/src/com/android/tests/apex/ApexdHostTest.java @@ -196,4 +196,32 @@ public class ApexdHostTest extends BaseHostJUnit4Test { getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex"); } } + + /** + * Verifies that content of {@code /data/apex/sessions/} is migrated to the {@code + * /metadata/apex/sessions}. + */ + @Test + public void testSessionsDirMigrationToMetadata() throws Exception { + assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported()); + assumeTrue("Device requires root", getDevice().isAdbRoot()); + + try { + getDevice().executeShellV2Command("mkdir -p /data/apex/sessions/1543"); + File file = File.createTempFile("foo", "bar"); + getDevice().pushFile(file, "/data/apex/sessions/1543/file"); + + // During boot sequence apexd will move /data/apex/sessions/1543/file to + // /metadata/apex/sessions/1543/file. + getDevice().reboot(); + assertWithMessage("Timed out waiting for device to boot").that( + getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue(); + + assertThat(getDevice().doesFileExist("/metadata/apex/sessions/1543/file")).isTrue(); + assertThat(getDevice().doesFileExist("/data/apex/sessions/1543/file")).isFalse(); + } finally { + getDevice().executeShellV2Command("rm -R /data/apex/sessions/1543"); + getDevice().executeShellV2Command("rm -R /metadata/apex/sessions/1543"); + } + } } |