1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "install/fuse_sdcard_install.h" 18 19 #include <dirent.h> 20 #include <signal.h> 21 #include <sys/mount.h> 22 #include <sys/stat.h> 23 #include <sys/types.h> 24 #include <sys/wait.h> 25 #include <unistd.h> 26 27 #include <algorithm> 28 #include <functional> 29 #include <memory> 30 #include <vector> 31 32 #include <android-base/logging.h> 33 #include <android-base/strings.h> 34 35 #include "bootloader_message/bootloader_message.h" 36 #include "fuse_provider.h" 37 #include "fuse_sideload.h" 38 #include "install/install.h" 39 #include "otautil/roots.h" 40 41 static constexpr const char* SDCARD_ROOT = "/sdcard"; 42 // How long (in seconds) we wait for the fuse-provided package file to 43 // appear, before timing out. 44 static constexpr int SDCARD_INSTALL_TIMEOUT = 10; 45 46 // Set the BCB to reboot back into recovery (it won't resume the install from 47 // sdcard though). 48 static void SetSdcardUpdateBootloaderMessage() { 49 std::vector<std::string> options; 50 std::string err; 51 if (!update_bootloader_message(options, &err)) { 52 LOG(ERROR) << "Failed to set BCB message: " << err; 53 } 54 } 55 56 // Returns the selected filename, or an empty string. 57 static std::string BrowseDirectory(const std::string& path, Device* device, RecoveryUI* ui) { 58 ensure_path_mounted(path); 59 60 std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir); 61 if (!d) { 62 PLOG(ERROR) << "error opening " << path; 63 return ""; 64 } 65 66 std::vector<std::string> dirs; 67 std::vector<std::string> entries{ "../" }; // "../" is always the first entry. 68 69 dirent* de; 70 while ((de = readdir(d.get())) != nullptr) { 71 std::string name(de->d_name); 72 73 if (de->d_type == DT_DIR) { 74 // Skip "." and ".." entries. 75 if (name == "." || name == "..") continue; 76 dirs.push_back(name + "/"); 77 } else if (de->d_type == DT_REG && android::base::EndsWithIgnoreCase(name, ".zip")) { 78 entries.push_back(name); 79 } 80 } 81 82 std::sort(dirs.begin(), dirs.end()); 83 std::sort(entries.begin(), entries.end()); 84 85 // Append dirs to the entries list. 86 entries.insert(entries.end(), dirs.begin(), dirs.end()); 87 88 std::vector<std::string> headers{ "Choose a package to install:", path }; 89 90 size_t chosen_item = 0; 91 while (true) { 92 chosen_item = ui->ShowMenu( 93 headers, entries, chosen_item, true, 94 std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2)); 95 96 // Return if WaitKey() was interrupted. 97 if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) { 98 return ""; 99 } 100 101 const std::string& item = entries[chosen_item]; 102 if (chosen_item == 0) { 103 // Go up but continue browsing (if the caller is BrowseDirectory). 104 return ""; 105 } 106 107 std::string new_path = path + "/" + item; 108 if (new_path.back() == '/') { 109 // Recurse down into a subdirectory. 110 new_path.pop_back(); 111 std::string result = BrowseDirectory(new_path, device, ui); 112 if (!result.empty()) return result; 113 } else { 114 // Selected a zip file: return the path to the caller. 115 return new_path; 116 } 117 } 118 119 // Unreachable. 120 } 121 122 static bool StartSdcardFuse(const std::string& path) { 123 auto file_data_reader = std::make_unique<FuseFileDataProvider>(path, 65536); 124 125 if (!file_data_reader->Valid()) { 126 return false; 127 } 128 129 // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so 130 // that our open file continues to work but new references see it as unmounted. 131 umount2("/sdcard", MNT_DETACH); 132 133 return run_fuse_sideload(std::move(file_data_reader)) == 0; 134 } 135 136 int ApplyFromSdcard(Device* device, RecoveryUI* ui) { 137 if (ensure_path_mounted(SDCARD_ROOT) != 0) { 138 LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n"; 139 return INSTALL_ERROR; 140 } 141 142 std::string path = BrowseDirectory(SDCARD_ROOT, device, ui); 143 if (path.empty()) { 144 LOG(ERROR) << "\n-- No package file selected.\n"; 145 ensure_path_unmounted(SDCARD_ROOT); 146 return INSTALL_ERROR; 147 } 148 149 ui->Print("\n-- Install %s ...\n", path.c_str()); 150 SetSdcardUpdateBootloaderMessage(); 151 152 // We used to use fuse in a thread as opposed to a process. Since accessing 153 // through fuse involves going from kernel to userspace to kernel, it leads 154 // to deadlock when a page fault occurs. (Bug: 26313124) 155 pid_t child; 156 if ((child = fork()) == 0) { 157 bool status = StartSdcardFuse(path); 158 159 _exit(status ? EXIT_SUCCESS : EXIT_FAILURE); 160 } 161 162 // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the fuse in child 163 // process is ready. 164 int result = INSTALL_ERROR; 165 int status; 166 bool waited = false; 167 for (int i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) { 168 if (waitpid(child, &status, WNOHANG) == -1) { 169 result = INSTALL_ERROR; 170 waited = true; 171 break; 172 } 173 174 struct stat sb; 175 if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &sb) == -1) { 176 if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT - 1) { 177 sleep(1); 178 continue; 179 } else { 180 LOG(ERROR) << "Timed out waiting for the fuse-provided package."; 181 result = INSTALL_ERROR; 182 kill(child, SIGKILL); 183 break; 184 } 185 } 186 187 result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, false, false, 0 /*retry_count*/, ui); 188 break; 189 } 190 191 if (!waited) { 192 // Calling stat() on this magic filename signals the fuse 193 // filesystem to shut down. 194 struct stat sb; 195 stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &sb); 196 197 waitpid(child, &status, 0); 198 } 199 200 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { 201 LOG(ERROR) << "Error exit from the fuse process: " << WEXITSTATUS(status); 202 } 203 204 ensure_path_unmounted(SDCARD_ROOT); 205 return result; 206 } 207