| /* |
| * Copyright (C) 2018 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 ANDROID_APEXD_APEXD_UTILS_H_ |
| #define ANDROID_APEXD_APEXD_UTILS_H_ |
| |
| #include <chrono> |
| #include <filesystem> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include <dirent.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include <android-base/chrono_utils.h> |
| #include <android-base/logging.h> |
| #include <android-base/result.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/strings.h> |
| #include <cutils/android_reboot.h> |
| |
| #include "apex_constants.h" |
| #include "string_log.h" |
| |
| using android::base::ErrnoError; |
| using android::base::Error; |
| using android::base::Result; |
| |
| namespace android { |
| namespace apex { |
| |
| inline int WaitChild(pid_t pid) { |
| int status; |
| pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); |
| |
| if (got_pid != pid) { |
| PLOG(WARNING) << "waitpid failed: wanted " << pid << ", got " << got_pid; |
| return 1; |
| } |
| |
| if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { |
| return 0; |
| } else { |
| return status; |
| } |
| } |
| |
| // TODO(ioffe): change to Result<void>? |
| inline int ForkAndRun(const std::vector<std::string>& args, |
| std::string* error_msg) { |
| LOG(DEBUG) << "Forking : " << android::base::Join(args, " "); |
| std::vector<const char*> argv; |
| argv.resize(args.size() + 1, nullptr); |
| std::transform(args.begin(), args.end(), argv.begin(), |
| [](const std::string& in) { return in.c_str(); }); |
| |
| // 3) Fork. |
| pid_t pid = fork(); |
| if (pid == -1) { |
| // Fork failed. |
| *error_msg = PStringLog() << "Unable to fork"; |
| return -1; |
| } |
| |
| if (pid == 0) { |
| execv(argv[0], const_cast<char**>(argv.data())); |
| PLOG(ERROR) << "execv failed"; |
| _exit(1); |
| } |
| |
| int rc = WaitChild(pid); |
| if (rc != 0) { |
| *error_msg = StringLog() << "Failed run: status=" << rc; |
| } |
| return rc; |
| } |
| |
| template <typename Fn> |
| Result<void> WalkDir(const std::string& path, Fn fn) { |
| namespace fs = std::filesystem; |
| std::error_code ec; |
| auto it = fs::directory_iterator(path, ec); |
| auto end = fs::directory_iterator(); |
| while (!ec && it != end) { |
| fn(*it); |
| it.increment(ec); |
| } |
| if (ec) { |
| return Error() << "Can't open " << path |
| << " for reading : " << ec.message(); |
| } |
| return {}; |
| } |
| |
| template <typename FilterFn> |
| Result<std::vector<std::string>> ReadDir(const std::string& path, FilterFn fn) { |
| namespace fs = std::filesystem; |
| |
| std::vector<std::string> ret; |
| auto status = WalkDir(path, [&](const fs::directory_entry& entry) { |
| if (fn(entry)) { |
| ret.push_back(entry.path()); |
| } |
| }); |
| if (!status.ok()) { |
| return status.error(); |
| } |
| return ret; |
| } |
| |
| inline bool IsEmptyDirectory(const std::string& path) { |
| auto res = ReadDir(path, [](auto _) { return true; }); |
| return res.ok() && res->empty(); |
| } |
| |
| inline Result<void> createDirIfNeeded(const std::string& path, mode_t mode) { |
| struct stat stat_data; |
| |
| if (stat(path.c_str(), &stat_data) != 0) { |
| if (errno == ENOENT) { |
| if (mkdir(path.c_str(), mode) != 0) { |
| return ErrnoError() << "Could not mkdir " << path; |
| } |
| } else { |
| return ErrnoError() << "Could not stat " << path; |
| } |
| } else { |
| if (!S_ISDIR(stat_data.st_mode)) { |
| return Error() << path << " exists and is not a directory."; |
| } |
| } |
| |
| // Need to manually call chmod because mkdir will create a folder with |
| // permissions mode & ~umask. |
| if (chmod(path.c_str(), mode) != 0) { |
| return ErrnoError() << "Could not chmod " << path; |
| } |
| |
| return {}; |
| } |
| |
| inline Result<void> DeleteDirContent(const std::string& path) { |
| auto files = ReadDir(path, [](auto _) { return true; }); |
| if (!files.ok()) { |
| return Error() << "Failed to delete " << path << " : " << files.error(); |
| } |
| for (const std::string& file : *files) { |
| if (unlink(file.c_str()) != 0) { |
| return ErrnoError() << "Failed to delete " << file; |
| } |
| } |
| return {}; |
| } |
| |
| inline Result<void> DeleteDir(const std::string& path) { |
| namespace fs = std::filesystem; |
| std::error_code ec; |
| fs::remove_all(path, ec); |
| if (ec) { |
| return Error() << "Failed to delete path " << path << " : " << ec.message(); |
| } |
| return {}; |
| } |
| |
| inline Result<ino_t> get_path_inode(const std::string& path) { |
| struct stat buf; |
| memset(&buf, 0, sizeof(buf)); |
| if (stat(path.c_str(), &buf) != 0) { |
| return ErrnoError() << "Failed to stat " << path; |
| } else { |
| return buf.st_ino; |
| } |
| } |
| |
| inline Result<bool> PathExists(const std::string& path) { |
| namespace fs = std::filesystem; |
| |
| std::error_code ec; |
| if (!fs::exists(fs::path(path), ec)) { |
| if (ec) { |
| return Error() << "Failed to access " << path << " : " << ec.message(); |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| inline void Reboot() { |
| LOG(INFO) << "Rebooting device"; |
| if (android_reboot(ANDROID_RB_RESTART2, 0, nullptr) != 0) { |
| LOG(ERROR) << "Failed to reboot device"; |
| } |
| } |
| |
| inline Result<void> WaitForFile(const std::string& path, |
| std::chrono::nanoseconds timeout) { |
| android::base::Timer t; |
| bool has_slept = false; |
| while (t.duration() < timeout) { |
| struct stat sb; |
| if (stat(path.c_str(), &sb) != -1) { |
| if (has_slept) { |
| LOG(INFO) << "wait for '" << path << "' took " << t; |
| } |
| return {}; |
| } |
| std::this_thread::sleep_for(5ms); |
| has_slept = true; |
| } |
| return ErrnoError() << "wait for '" << path << "' timed out and took " << t; |
| } |
| |
| inline Result<std::vector<std::string>> GetSubdirs(const std::string& path) { |
| namespace fs = std::filesystem; |
| auto filter_fn = [](const std::filesystem::directory_entry& entry) { |
| std::error_code ec; |
| bool result = entry.is_directory(ec); |
| if (ec) { |
| LOG(ERROR) << "Failed to check is_directory : " << ec.message(); |
| return false; |
| } |
| return result; |
| }; |
| return ReadDir(path, filter_fn); |
| } |
| |
| inline Result<std::vector<std::string>> GetDeUserDirs() { |
| return GetSubdirs(kDeNDataDir); |
| } |
| |
| } // namespace apex |
| } // namespace android |
| |
| #endif // ANDROID_APEXD_APEXD_UTILS_H_ |