try_update_binary

本文深入分析了OTA升级过程中的关键步骤,重点介绍了recovery模式下的update-binary文件如何被解析及执行,包括其内部函数try_update_binary的具体实现细节。

recovery代码分析之三:try_update_binary

2013年02月04日  ⁄ 综合 ⁄ 共 3675字 ⁄ 字号  小 中 大  ⁄ 评论关闭
id="cproIframe_u1507428" width="300" height="250" src="http://pos.baidu.com/acom?adn=0&at=103&aurl=&cad=1&ccd=32&cec=UTF-8&cfv=11&ch=0&col=zh-CN&conOP=0&cpa=1&dai=2&dis=0&ltr=http%3A%2F%2Fwww.baidu.com%2Fs%3Fie%3Dutf-8%26f%3D8%26rsv_bp%3D1%26tn%3Dbaidu%26wd%3Drecovery%25E4%25B8%25AD%25E8%2584%259A%25E6%259C%25AC%25E8%25A7%25A3%25E6%259E%2590%26rsv_enter%3D0%26rsv_sug3%3D5%26rsv_sug4%3D1269%26rsv_sug2%3D0%26inputT%3D4798&ltu=http%3A%2F%2Fwww.xuebuyuan.com%2F1554719.html&lunum=6&n=83099053_cpr&pcs=1366x599&pis=10000x10000&ps=326x909&psr=1366x768&pss=1366x346&qn=82e7d9eabe672930&rad=&rsi0=300&rsi1=250&rsi5=4&rss0=%23FFFFFF&rss1=%23FFFFFF&rss2=%230080c0&rss3=%23444444&rss4=%23008000&rss5=&rss6=%23e10900&rss7=&scale=&skin=&td_id=1507428&tn=text_default_300_250&tpr=1411638264064&ts=1&xuanting=0&dtm=BAIDU_DUP2_SETJSONADSLOT&dc=2&di=u1507428" align="center,center" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="margin: 0px; padding: 0px; border-width: 0px; background-color: transparent;">

        OTA升级包路径META-INF\com\google\android中,存在着两个关键的文件:update-script和update-binary。在这两个脚本文件中,update-script记载着系统升级所需要执行的命令(如图1所示),而update-binary则是对于每条命令的解析。进入recovery模式后,系统将会执行文件中记载的命令,完成升级。

图1 update-script内容截图

http://blog.youkuaiyun.com/liudekuan/article/details/8707044可知,在文件./bootable/recovery/install.c中定义了对应于每条命令的执行函数(如图2所示),即在recovery模式下

,系统会将这些命令转换为相应的函数去执行。而RegisterInstallFunctions函数在./bootable/recovery/update.c中被调用,在源码编译过程中,./bootable/recovery/updater目录下的代码被编译为可执行文件:out/target/product/sp8825ea/system/bin/updater,供recovery模式下系统使用。

图2 函数注册

static int
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
    const ZipEntry* binary_entry =
            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
    if (binary_entry == NULL) {
        mzCloseZipArchive(zip);
        return INSTALL_CORRUPT;
    }

    char* binary = "/tmp/update_binary";
    unlink(binary);
    int fd = creat(binary, 0755);
    if (fd < 0) {
        mzCloseZipArchive(zip);
        LOGE("Can't make %s\n", binary);
        return INSTALL_ERROR;
    }
    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
    close(fd);
    mzCloseZipArchive(zip);

    if (!ok) {
        LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
        return INSTALL_ERROR;
    }

    int pipefd[2];
    pipe(pipefd);

    // When executing the update binary contained in the package, the
    // arguments passed are:
    //
    //   - the version number for this interface
    //
    //   - an fd to which the program can write in order to update the
    //     progress bar.  The program can write single-line commands:
    //
    //        progress <frac> <secs>
    //            fill up the next <frac> part of of the progress bar
    //            over <secs> seconds.  If <secs> is zero, use
    //            set_progress commands to manually control the
    //            progress of this segment of the bar
    //
    //        set_progress <frac>
    //            <frac> should be between 0.0 and 1.0; sets the
    //            progress bar within the segment defined by the most
    //            recent progress command.
    //
    //        firmware <"hboot"|"radio"> <filename>
    //            arrange to install the contents of <filename> in the
    //            given partition on reboot.
    //
    //            (API v2: <filename> may start with "PACKAGE:" to
    //            indicate taking a file from the OTA package.)
    //
    //            (API v3: this command no longer exists.)
    //
    //        ui_print <string>
    //            display <string> on the screen.
    //
    //   - the name of the package zip file.
    //

    char** args = malloc(sizeof(char*) * 5);
    args[0] = binary;
    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
    args[2] = malloc(10);
    sprintf(args[2], "%d", pipefd[1]);
    args[3] = (char*)path;
    args[4] = NULL;

    pid_t pid = fork();
    if (pid == 0) {
        close(pipefd[0]);
        execv(binary, args);
        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
        _exit(-1);
    }
    close(pipefd[1]);

    *wipe_cache = 0;

    char buffer[1024];
    FILE* from_child = fdopen(pipefd[0], "r");
    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
        char* command = strtok(buffer, " \n");
        if (command == NULL) {
            continue;
        } else if (strcmp(command, "progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            char* seconds_s = strtok(NULL, " \n");

            float fraction = strtof(fraction_s, NULL);
            int seconds = strtol(seconds_s, NULL, 10);

            ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
                             seconds);
        } else if (strcmp(command, "set_progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            float fraction = strtof(fraction_s, NULL);
            ui_set_progress(fraction);
        } else if (strcmp(command, "ui_print") == 0) {
            char* str = strtok(NULL, "\n");
            if (str) {
                ui_print("%s", str);
            } else {
                ui_print("\n");
            }
        } else if (strcmp(command, "wipe_cache") == 0) {
            *wipe_cache = 1;
        } else {
            LOGE("unknown command [%s]\n", command);
        }
    }
    fclose(from_child);

    int status;
    waitpid(pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
        return INSTALL_ERROR;
    }

    return INSTALL_SUCCESS;
}

代码段1 try_update_binary函数

        ./bootable/recovery/install.c中的try_update_binary函数,是真正实现读取升级包中的脚本文件并执行相应的函数的地方。在此函数中,通过调用fork函数创建出一个子进程(代码第72行),在子进程中开始读取并执行升级脚本文件(代码73-78)。在此需要注意的是函数fork的用法,fork被调用一次,将做两次返回,在父进程中返回的是子进程的进程ID,为正数;而在子进程中,则返回0。因此代码73-78事实是子进程中所进行的操作,即execv(binary,
args)。子进程创建成功后,开始执行升级代码,并通过管道与父进程交互(代码27-28,创建管道,并将pipefd[1]作为参数传递给子进程,子进程则将相关信息写入到此管道描述符中);而父进程则通过读取子进程传递过来的信息更新UI(代码84-114)。

import re import os import json import hashlib import time from datetime import datetime, timedelta from collections import defaultdict from typing import List, Dict, Optional, Tuple from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileCreatedEvent class HexMailParser: """解析FOXMAIL十六进制邮件数据""" def __init__(self): # 定义常见邮件头部的正则表达式模式 self.header_patterns = { 'from': re.compile(rb'From: ([^\r\n]+)'), 'to': re.compile(rb'To: ([^\r\n]+)'), 'subject': re.compile(rb'Subject: ([^\r\n]+)'), 'date': re.compile(rb'Date: ([^\r\n]+)'), 'message_id': re.compile(rb'Message-ID: ([^\r\n]+)') } # 十六进制转储行的正则表达式 self.hex_line_pattern = re.compile(rb'([0-9A-F]+)\s+((?:[0-9A-F]{2}\s+)+)(.*)') def parse_hex_dump(self, hex_data: bytes) -> bytes: """将十六进制转储数据转换为原始二进制数据""" binary_data = bytearray() lines = hex_data.split(b'\n') for line in lines: line = line.strip() if not line: continue # 匹配十六进制转储行格式 match = self.hex_line_pattern.match(line) if match: # 提取十六进制字节部分 hex_part = match.group(2).replace(b' ', b'') try: # 转换为二进制 binary_part = bytes.fromhex(hex_part.decode('ascii')) binary_data.extend(binary_part) except ValueError: # 忽略无法解析的行 continue return bytes(binary_data) def extract_headers(self, binary_data: bytes) -> Dict[str, str]: """从二进制数据中提取邮件头部信息""" headers = {} # 查找邮件头部区域(在空行之前) header_end = binary_data.find(b'\r\n\r\n') if header_end == -1: header_end = binary_data.find(b'\n\n') if header_end != -1: header_data = binary_data[:header_end] # 提取各个头部字段 for field, pattern in self.header_patterns.items(): match = pattern.search(header_data) if match: value = match.group(1).decode('utf-8', errors='ignore') headers[field] = value return headers def extract_body(self, binary_data: bytes) -> str: """从二进制数据中提取邮件正文""" # 查找邮件头部结束位置 header_end = binary_data.find(b'\r\n\r\n') if header_end == -1: header_end = binary_data.find(b'\n\n') if header_end != -1: body_data = binary_data[header_end + 4:] # +4 跳过空行 try: # 尝试多种编码解码 for encoding in ['utf-8', 'gbk', 'latin-1']: try: return body_data.decode(encoding) except UnicodeDecodeError: continue except: pass # 如果无法解码,返回原始字节的十六进制表示 return body_data.hex() def parse(self, hex_data: bytes) -> Dict: """解析十六进制邮件数据并返回结构化信息""" binary_data = self.parse_hex_dump(hex_data) headers = self.extract_headers(binary_data) body = self.extract_body(binary_data) # 解析日期 if 'date' in headers: try: # 尝试常见的日期格式 date_formats = [ '%a, %d %b %Y %H:%M:%S %z', '%a, %d %b %Y %H:%M:%S %Z', '%d %b %Y %H:%M:%S %z', ] parsed_date = None for fmt in date_formats: try: parsed_date = datetime.strptime(headers['date'], fmt) break except ValueError: continue if parsed_date: headers['parsed_date'] = parsed_date.isoformat() except: pass return { 'headers': headers, 'body': body, 'hash': hashlib.sha256(binary_data).hexdigest(), 'file_path': None # 存储邮件文件的原始路径 } class MailIndexer: """邮件索引管理系统""" def __init__(self, index_dir: str, mail_storage_dir: str, important_folders: List[str] = None): self.index_dir = index_dir self.mail_storage_dir = mail_storage_dir self.important_folders = important_folders or [] self.parser = HexMailParser() # 记录上次全量更新时间 self.last_full_update = None # 创建索引目录 os.makedirs(index_dir, exist_ok=True) # 初始化索引 self.index = { 'by_hash': {}, # 按哈希值索引 'by_sender': defaultdict(list), # 按发件人索引 'by_recipient': defaultdict(list), # 按收件人索引 'by_subject': defaultdict(list), # 按主题索引 'by_date': {}, # 按日期索引 'by_message_id': {} # 按Message-ID索引 } # 加载现有索引 self._load_index() def _load_index(self): """从磁盘加载现有索引""" for filename in os.listdir(self.index_dir): if filename.endswith('.json'): filepath = os.path.join(self.index_dir, filename) try: with open(filepath, 'r', encoding='utf-8') as f: mail_data = json.load(f) self._add_to_index(mail_data) except Exception as e: print(f"Error loading index file {filename}: {e}") def _save_mail(self, mail_data: Dict): """保存邮件数据到磁盘""" mail_hash = mail_data['hash'] filename = f"{mail_hash}.json" filepath = os.path.join(self.index_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: json.dump(mail_data, f, ensure_ascii=False, indent=2) def _add_to_index(self, mail_data: Dict): """将邮件添加到内存索引""" mail_hash = mail_data['hash'] headers = mail_data['headers'] self.index['by_hash'][mail_hash] = mail_data # 按发件人索引 if 'from' in headers: self.index['by_sender'][headers['from']].append(mail_hash) # 按收件人索引 if 'to' in headers: recipients = headers['to'].split(',') for recipient in recipients: recipient = recipient.strip() if recipient: self.index['by_recipient'][recipient].append(mail_hash) # 按主题索引 if'subject' in headers: self.index['by_subject'][headers['subject']].append(mail_hash) # 按日期索引 if 'parsed_date' in headers: date = headers['parsed_date'].split('T')[0] # 提取日期部分 if date not in self.index['by_date']: self.index['by_date'][date] = [] self.index['by_date'][date].append(mail_hash) # 按Message-ID索引 if'message_id' in headers: self.index['by_message_id'][headers['message_id']] = mail_hash def index_mail(self, file_path: str) -> Optional[Dict]: """索引单个邮件文件""" try: with open(file_path, 'rb') as f: hex_data = f.read() mail_data = self.parser.parse(hex_data) mail_data['file_path'] = file_path # 记录原始文件路径 # 检查是否已索引 if mail_data['hash'] in self.index['by_hash']: existing_mail = self.index['by_hash'][mail_data['hash']] # 如果文件路径不同,可能是同一邮件的副本 if existing_mail.get('file_path') != file_path: print(f"Found duplicate mail with different path: {file_path}") return None return None # 添加到索引并保存 self._add_to_index(mail_data) self._save_mail(mail_data) print(f"Indexed mail: {mail_data['headers'].get('subject', '(No subject)')}") return mail_data except Exception as e: print(f"Error indexing mail {file_path}: {e}") return None def full_index_update(self) -> int: """执行全量索引更新""" start_time = time.time() count = 0 print(f"Starting full index update...") for root, dirs, files in os.walk(self.mail_storage_dir): for file in files: file_path = os.path.join(root, file) mail_data = self.index_mail(file_path) if mail_data: count += 1 self.last_full_update = datetime.now() duration = time.time() - start_time print(f"Full index update completed: {count} new/updated mails, took {duration:.2f} seconds") return count def is_important_folder(self, file_path: str) -> bool: """检查文件是否在重要文件夹中""" if not self.important_folders: return False for folder in self.important_folders: if folder in file_path: return True return False def search_by_sender(self, sender: str) -> List[Dict]: """按发件人搜索邮件""" mail_hashes = self.index['by_sender'].get(sender, []) return [self.index['by_hash'][h] for h in mail_hashes] def search_by_recipient(self, recipient: str) -> List[Dict]: """按收件人搜索邮件""" mail_hashes = self.index['by_recipient'].get(recipient, []) return [self.index['by_hash'][h] for h in mail_hashes] def search_by_subject(self, subject: str) -> List[Dict]: """按主题搜索邮件""" mail_hashes = self.index['by_subject'].get(subject, []) return [self.index['by_hash'][h] for h in mail_hashes] def search_by_date(self, date: str) -> List[Dict]: """按日期搜索邮件(格式:YYYY-MM-DD)""" mail_hashes = self.index['by_date'].get(date, []) return [self.index['by_hash'][h] for h in mail_hashes] def get_mail_by_hash(self, mail_hash: str) -> Optional[Dict]: """按哈希值获取邮件""" return self.index['by_hash'].get(mail_hash) def get_mail_by_message_id(self, message_id: str) -> Optional[Dict]: """按Message-ID获取邮件""" mail_hash = self.index['by_message_id'].get(message_id) if mail_hash: return self.index['by_hash'].get(mail_hash) return None class MailChangeHandler(FileSystemEventHandler): """处理邮件目录变化的事件处理器""" def __init__(self, indexer: MailIndexer): self.indexer = indexer def on_created(self, event): """处理文件创建事件""" if not event.is_directory: print(f"New file detected: {event.src_path}") if self.indexer.is_important_folder(event.src_path): print("File is in important folder, indexing immediately...") self.indexer.index_mail(event.src_path) def on_modified(self, event): """处理文件修改事件""" if not event.is_directory: print(f"File modified: {event.src_path}") if self.indexer.is_important_folder(event.src_path): print("File is in important folder, reindexing immediately...") self.indexer.index_mail(event.src_path) class MailIndexManager: """邮件索引管理器,整合定期更新和实时监听功能""" def __init__(self, index_dir: str, mail_storage_dir: str, important_folders: List[str] = None, full_update_interval: int = 24): """ 初始化邮件索引管理器 参数: index_dir: 索引存储目录 mail_storage_dir: 邮件存储目录 important_folders: 重要文件夹列表,这些文件夹的变化会被实时监听 full_update_interval: 全量更新间隔(小时) """ self.indexer = MailIndexer(index_dir, mail_storage_dir, important_folders) self.full_update_interval = full_update_interval self.observer = None def start_monitoring(self): """启动文件系统监听""" if not self.indexer.important_folders: print("No important folders specified, skipping real-time monitoring") return print("Starting real-time monitoring for important folders...") event_handler = MailChangeHandler(self.indexer) self.observer = Observer() for folder in self.indexer.important_folders: folder_path = os.path.join(self.indexer.mail_storage_dir, folder) if os.path.exists(folder_path): self.observer.schedule(event_handler, path=folder_path, recursive=True) print(f"Monitoring folder: {folder_path}") else: print(f"Warning: Important folder not found: {folder_path}") self.observer.start() def stop_monitoring(self): """停止文件系统监听""" if self.observer: self.observer.stop() self.observer.join() print("Real-time monitoring stopped") def need_full_update(self) -> bool: """检查是否需要进行全量更新""" if not self.indexer.last_full_update: return True elapsed = datetime.now() - self.indexer.last_full_update return elapsed.total_seconds() >= self.full_update_interval * 3600 def run_scheduled_update(self): """运行计划更新""" if self.need_full_update(): print(f"Scheduled full update triggered (last update: {self.indexer.last_full_update})") self.indexer.full_index_update() else: print(f"Full update not needed yet (last update: {self.indexer.last_full_update})") def run(self, check_interval: int = 3600): """运行管理器主循环 参数: check_interval: 检查间隔(秒),用于检查是否需要全量更新 """ try: # 启动时先进行一次全量更新 self.indexer.full_index_update() # 启动实时监听 self.start_monitoring() print(f"Mail index manager running. Full updates every {self.full_update_interval} hours.") print(f"Checking for updates every {check_interval} seconds.") # 主循环 while True: time.sleep(check_interval) self.run_scheduled_update() except KeyboardInterrupt: print("Shutting down...") finally: # 确保清理资源 self.stop_monitoring() # 使用示例 if __name__ == "__main__": index_dir = r'D:\YOUJIANSUOYIN' mail_storage_dir = r'D:\Foxmail\Storage\sh-op9@isaco-sh.com\Mails' # 指定重要文件夹(相对于mail_storage_dir的路径) important_folders = [ 'Inbox', # 收件箱 'Sent Items', # 已发送邮件 'Important' # 重要文件夹 ] # 创建管理器实例(全量更新间隔为24小时) manager = MailIndexManager( index_dir=index_dir, mail_storage_dir=mail_storage_dir, important_folders=important_folders, full_update_interval=24 ) # 运行管理器(检查间隔为1小时) manager.run(check_interval=3600)
05-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值