// This program takes a file on an ext4 filesystem and produces a list // of the blocks that file occupies, which enables the file contents // to be read directly from the block device without mounting the // filesystem. // // If the filesystem is using an encrypted block device, it will also // read the file and rewrite it to the same blocks of the underlying // (unencrypted) block device, so the file contents can be read // without the need for the decryption key. // // The output of this program is a "block map" which looks like this: // // /dev/block/platform/msm_sdcc.1/by-name/userdata # block device // 49652 4096 # file size in bytes, block size // 3 # count of block ranges // 1000 1008 # block range 0 // 2100 2102 # ... block range 1 // 30 33 # ... block range 2 // // Each block range represents a half-open interval; the line "30 33" // reprents the blocks [30, 31, 32]. // // Recovery can take this block map file and retrieve the underlying // file data to use as an update package.
using android::fs_mgr::Fstab; using android::fs_mgr::ReadDefaultFstab;
static constexpr int WINDOW_SIZE = 5; static constexpr int FIBMAP_RETRY_LIMIT = 3;
// uncrypt provides three services: SETUP_BCB, CLEAR_BCB and UNCRYPT. // // SETUP_BCB and CLEAR_BCB services use socket communication and do not rely // on /cache partitions. They will handle requests to reboot into recovery // (for applying updates for non-A/B devices, or factory resets for all // devices). // // UNCRYPT service still needs files on /cache partition (UNCRYPT_PATH_FILE // and CACHE_BLOCK_MAP). It will be working (and needed) only for non-A/B // devices, on which /cache partitions always exist. staticconst std::string CACHE_BLOCK_MAP = "/cache/recovery/block.map"; staticconst std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file"; staticconst std::string UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; staticconst std::string UNCRYPT_SOCKET = "uncrypt";
staticvoid add_block_to_ranges(std::vector<int>& ranges, int new_block) { if (!ranges.empty() && new_block == ranges.back()) { // If the new block comes immediately after the current range, // all we have to do is extend the current range.
++ranges.back();
} else { // We need to start a new range.
ranges.push_back(new_block);
ranges.push_back(new_block + 1);
}
}
// Looks for a volume whose mount point is the prefix of path and returns its block device or an // empty string. Sets encryption flags accordingly. static std::string FindBlockDevice(const std::string& path, bool* encryptable, bool* encrypted, bool* f2fs_fs) { // Ensure f2fs_fs is set to false first.
*f2fs_fs = false;
for (constauto& entry : fstab) { if (entry.mount_point.empty()) { continue;
} if (android::base::StartsWith(path, entry.mount_point + "/")) {
*encrypted = false;
*encryptable = false; if (entry.is_encryptable() || entry.fs_mgr_flags.file_encryption) {
*encryptable = true; if (android::base::GetProperty("ro.crypto.state", "") == "encrypted") {
*encrypted = true;
}
} if (entry.fs_type == "f2fs") {
*f2fs_fs = true;
} return entry.blk_device;
}
}
return"";
}
staticbool write_status_to_socket(int status, int socket) { // If socket equals -1, uncrypt is in debug mode without socket communication. // Skip writing and return success. if (socket == -1) { returntrue;
} int status_out = htonl(status); return android::base::WriteFully(socket, &status_out, sizeof(int));
}
// Parses the given path file to find the update package name. staticbool FindUncryptPackage(const std::string& uncrypt_path_file, std::string* package_name) {
CHECK(package_name != nullptr);
std::string uncrypt_path; if (!android::base::ReadFileToString(uncrypt_path_file, &uncrypt_path)) {
PLOG(ERROR) << "failed to open \"" << uncrypt_path_file << "\""; returnfalse;
}
// Remove the trailing '\n' if present.
*package_name = android::base::Trim(uncrypt_path); returntrue;
}
staticint RetryFibmap(int fd, const std::string& name, int* block, constint head_block) {
CHECK(block != nullptr); for (size_t i = 0; i < FIBMAP_RETRY_LIMIT; i++) { if (fsync(fd) == -1) {
PLOG(ERROR) << "failed to fsync \"" << name << "\""; return kUncryptFileSyncError;
} if (ioctl(fd, FIBMAP, block) != 0) {
PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError;
} if (*block != 0) { return kUncryptNoError;
}
sleep(1);
}
LOG(ERROR) << "fibmap of " << head_block << " always returns 0"; return kUncryptIoctlError;
}
// Make sure we can write to the socket. if (!write_status_to_socket(0, socket)) {
LOG(ERROR) << "failed to write to socket " << socket; return kUncryptSocketWriteError;
}
struct stat sb; if (stat(path.c_str(), &sb) != 0) {
PLOG(ERROR) << "failed to stat " << path; return kUncryptFileStatError;
}
std::string s = android::base::StringPrintf("%s\n%" PRId64 " %" PRId64 "\n", blk_dev.c_str(), static_cast<int64_t>(sb.st_size), static_cast<int64_t>(sb.st_blksize)); if (!android::base::WriteStringToFd(s, mapfd)) {
PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError;
}
std::vector<std::vector<unsignedchar>> buffers; if (encrypted) {
buffers.resize(WINDOW_SIZE, std::vector<unsignedchar>(sb.st_blksize));
} int head_block = 0; int head = 0, tail = 0;
android::base::unique_fd fd(open(path.c_str(), O_RDWR)); if (fd == -1) {
PLOG(ERROR) << "failed to open " << path << " for reading"; return kUncryptFileOpenError;
}
android::base::unique_fd wfd; if (encrypted) {
wfd.reset(open(blk_dev.c_str(), O_WRONLY)); if (wfd == -1) {
PLOG(ERROR) << "failed to open " << blk_dev << " for writing"; return kUncryptBlockOpenError;
}
}
// F2FS-specific ioctl // It requires the below kernel commit merged in v4.16-rc1. // 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file") // In android-4.4, // 56ee1e817908 ("f2fs: updates on v4.16-rc1") // In android-4.9, // 2f17e34672a8 ("f2fs: updates on v4.16-rc1") // In android-4.14, // ce767d9a55bc ("f2fs: updates on v4.16-rc1") #ifndef F2FS_IOC_SET_PIN_FILE #ifndef F2FS_IOCTL_MAGIC #define F2FS_IOCTL_MAGIC 0xf5 #endif #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32) #define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32) #endif if (f2fs_fs) {
__u32 set = 1; int error = ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set); // Don't break the old kernels which don't support it. if (error && errno != ENOTTY && errno != ENOTSUP) {
PLOG(ERROR) << "Failed to set pin_file for f2fs: " << path << " on " << blk_dev; return kUncryptIoctlError;
}
}
off64_t pos = 0; int last_progress = 0; while (pos < sb.st_size) { // Update the status file, progress must be between [0, 99]. int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size))); if (progress > last_progress) {
last_progress = progress;
write_status_to_socket(progress, socket);
}
if ((tail+1) % WINDOW_SIZE == head) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) {
PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError;
}
if (block == 0) {
LOG(ERROR) << "failed to find block " << head_block << ", retrying"; int error = RetryFibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error;
}
}
add_block_to_ranges(ranges, block); if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError;
}
}
head = (head + 1) % WINDOW_SIZE;
++head_block;
}
// read next block to tail if (encrypted) {
size_t to_read = static_cast<size_t>(
std::min(static_cast<off64_t>(sb.st_blksize), sb.st_size - pos)); if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) {
PLOG(ERROR) << "failed to read " << path; return kUncryptReadError;
}
pos += to_read;
} else { // If we're not encrypting; we don't need to actually read // anything, just skip pos forward as if we'd read a // block.
pos += sb.st_blksize;
}
tail = (tail+1) % WINDOW_SIZE;
}
while (head != tail) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) {
PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError;
}
if (block == 0) {
LOG(ERROR) << "failed to find block " << head_block << ", retrying"; int error = RetryFibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error;
}
}
add_block_to_ranges(ranges, block); if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError;
}
}
head = (head + 1) % WINDOW_SIZE;
++head_block;
}
if (!android::base::WriteStringToFd(
android::base::StringPrintf("%zu\n", ranges.size() / 2), mapfd)) {
PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError;
} for (size_t i = 0; i < ranges.size(); i += 2) { if (!android::base::WriteStringToFd(
android::base::StringPrintf("%d %d\n", ranges[i], ranges[i+1]), mapfd)) {
PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError;
}
}
if (fsync(mapfd) == -1) {
PLOG(ERROR) << "failed to fsync \"" << tmp_map_file << "\""; return kUncryptFileSyncError;
} if (close(mapfd.release()) == -1) {
PLOG(ERROR) << "failed to close " << tmp_map_file; return kUncryptFileCloseError;
}
if (encrypted) { if (fsync(wfd) == -1) {
PLOG(ERROR) << "failed to fsync \"" << blk_dev << "\""; return kUncryptFileSyncError;
} if (close(wfd.release()) == -1) {
PLOG(ERROR) << "failed to close " << blk_dev; return kUncryptFileCloseError;
}
}
if (rename(tmp_map_file.c_str(), map_file.c_str()) == -1) {
PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; return kUncryptFileRenameError;
} // Sync dir to make rename() result written to disk.
std::string dir_name = android::base::Dirname(map_file);
android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); if (dfd == -1) {
PLOG(ERROR) << "failed to open dir " << dir_name; return kUncryptFileOpenError;
} if (fsync(dfd) == -1) {
PLOG(ERROR) << "failed to fsync " << dir_name; return kUncryptFileSyncError;
} if (close(dfd.release()) == -1) {
PLOG(ERROR) << "failed to close " << dir_name; return kUncryptFileCloseError;
} return0;
}
staticint Uncrypt(const std::string& input_path, const std::string& map_file, int socket) {
LOG(INFO) << "update package is \"" << input_path << "\"";
// Turn the name of the file we're supposed to convert into an absolute path, so we can find what // filesystem it's on.
std::string path; if (!android::base::Realpath(input_path, &path)) {
PLOG(ERROR) << "Failed to convert \"" << input_path << "\" to absolute path"; return kUncryptRealpathFindError;
}
// If the filesystem it's on isn't encrypted, we only produce the block map, we don't rewrite the // file contents (it would be pointless to do so).
LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no");
LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no");
// Recovery supports installing packages from 3 paths: /cache, /data, and /sdcard. (On a // particular device, other locations may work, but those are three we actually expect.) // // On /data we want to convert the file to a block map so that we can read the package without // mounting the partition. On /cache and /sdcard we leave the file alone. if (android::base::StartsWith(path, "/data/")) {
LOG(INFO) << "writing block map " << map_file; return ProductBlockMap(path, map_file, blk_dev, encrypted, f2fs_fs, socket);
}
return0;
}
staticvoid log_uncrypt_error_code(UncryptErrorCode error_code) { if (!android::base::WriteStringToFile(android::base::StringPrintf( "uncrypt_error: %d\n", error_code), UNCRYPT_STATUS)) {
PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
}
}
staticbool uncrypt_wrapper(constchar* input_path, constchar* map_file, constint socket) { // Initialize the uncrypt error to kUncryptErrorPlaceholder.
log_uncrypt_error_code(kUncryptErrorPlaceholder);
std::string package; if (input_path == nullptr) { if (!FindUncryptPackage(UNCRYPT_PATH_FILE, &package)) {
write_status_to_socket(-1, socket); // Overwrite the error message.
log_uncrypt_error_code(kUncryptPackageMissingError); returnfalse;
}
input_path = package.c_str();
}
CHECK(map_file != nullptr);
auto start = std::chrono::system_clock::now(); int status = Uncrypt(input_path, map_file, socket);
std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; int count = static_cast<int>(duration.count());
std::string uncrypt_message = android::base::StringPrintf("uncrypt_time: %d\n", count); if (status != 0) { // Log the time cost and error code if uncrypt fails.
uncrypt_message += android::base::StringPrintf("uncrypt_error: %d\n", status); if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) {
PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS;
}
// c3. The socket is created by init when starting the service. uncrypt // will use the socket to communicate with its caller.
android::base::unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str())); if (service_socket == -1) {
PLOG(ERROR) << "failed to open socket \"" << UNCRYPT_SOCKET << "\"";
log_uncrypt_error_code(kUncryptSocketOpenError); return1;
}
fcntl(service_socket, F_SETFD, FD_CLOEXEC);
if (listen(service_socket, 1) == -1) {
PLOG(ERROR) << "failed to listen on socket " << service_socket.get();
log_uncrypt_error_code(kUncryptSocketListenError); return1;
}
android::base::unique_fd socket_fd(accept4(service_socket, nullptr, nullptr, SOCK_CLOEXEC)); if (socket_fd == -1) {
PLOG(ERROR) << "failed to accept on socket " << service_socket.get();
log_uncrypt_error_code(kUncryptSocketAcceptError); return1;
}
bool success = false; switch (action) { case UNCRYPT:
success = uncrypt_wrapper(input_path, map_file, socket_fd); break; case SETUP_BCB:
success = setup_bcb(socket_fd); break; case CLEAR_BCB:
success = clear_bcb(socket_fd); break; default: // Should never happen.
LOG(ERROR) << "Invalid uncrypt action code: " << action; return1;
}
// c13. Read a 4-byte code from the client before uncrypt exits. This is to // ensure the client to receive the last status code before the socket gets // destroyed. int code; if (android::base::ReadFully(socket_fd, &code, 4)) {
LOG(INFO) << " received " << code << ", exiting now";
} else {
PLOG(ERROR) << "failed to read the code";
} return success ? 0 : 1;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-07-01)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.