Move non-AB code from bootable/recovery to bootable/deprecated-ota
Bug: 324360816 Test: th Change-Id: I3d82d9031446be355d8a1d077ab83283c7cc769c
This commit is contained in:
parent
6efecfa531
commit
801ba0ccaa
66 changed files with 15944 additions and 0 deletions
190
NOTICE
Normal file
190
NOTICE
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
|
||||||
|
Copyright (c) 2005-2008, 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
3
OWNERS
Normal file
3
OWNERS
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
zhangkelvin@google.com
|
||||||
|
akailash@google.com
|
||||||
|
|
201
applypatch/Android.bp
Normal file
201
applypatch/Android.bp
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
// Copyright (C) 2017 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.
|
||||||
|
|
||||||
|
package {
|
||||||
|
default_applicable_licenses: ["bootable_recovery_applypatch_license"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added automatically by a large-scale-change
|
||||||
|
// See: http://go/android-license-faq
|
||||||
|
license {
|
||||||
|
name: "bootable_recovery_applypatch_license",
|
||||||
|
visibility: [":__subpackages__"],
|
||||||
|
license_kinds: [
|
||||||
|
"SPDX-license-identifier-Apache-2.0",
|
||||||
|
],
|
||||||
|
license_text: [
|
||||||
|
"NOTICE",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_defaults {
|
||||||
|
name: "applypatch_defaults",
|
||||||
|
|
||||||
|
cflags: [
|
||||||
|
"-D_FILE_OFFSET_BITS=64",
|
||||||
|
"-DZLIB_CONST",
|
||||||
|
"-Wall",
|
||||||
|
"-Werror",
|
||||||
|
],
|
||||||
|
|
||||||
|
local_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libapplypatch",
|
||||||
|
|
||||||
|
host_supported: true,
|
||||||
|
vendor_available: true,
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"applypatch_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"applypatch.cpp",
|
||||||
|
"bspatch.cpp",
|
||||||
|
"freecache.cpp",
|
||||||
|
"imgpatch.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
export_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libbase",
|
||||||
|
"libbspatch",
|
||||||
|
"libbz",
|
||||||
|
"libedify",
|
||||||
|
"libotautil",
|
||||||
|
"libz_stable",
|
||||||
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"libcrypto",
|
||||||
|
],
|
||||||
|
|
||||||
|
target: {
|
||||||
|
darwin: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libapplypatch_modes",
|
||||||
|
vendor_available: true,
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"applypatch_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"applypatch_modes.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libapplypatch",
|
||||||
|
"libbase",
|
||||||
|
"libedify",
|
||||||
|
"libotautil",
|
||||||
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"libcrypto",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_binary {
|
||||||
|
name: "applypatch",
|
||||||
|
vendor: true,
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"applypatch_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"applypatch_main.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libapplypatch_modes",
|
||||||
|
"libapplypatch",
|
||||||
|
"libedify",
|
||||||
|
"libotautil",
|
||||||
|
|
||||||
|
// External dependencies.
|
||||||
|
"libbspatch",
|
||||||
|
"libbrotli",
|
||||||
|
"libbz",
|
||||||
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"libbase",
|
||||||
|
"libcrypto",
|
||||||
|
"liblog",
|
||||||
|
"libz_stable",
|
||||||
|
"libziparchive",
|
||||||
|
],
|
||||||
|
|
||||||
|
init_rc: [
|
||||||
|
"vendor_flash_recovery.rc",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libimgdiff",
|
||||||
|
host_supported: true,
|
||||||
|
defaults: [
|
||||||
|
"applypatch_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"imgdiff.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
export_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libbase",
|
||||||
|
"libbsdiff",
|
||||||
|
"libdivsufsort",
|
||||||
|
"libdivsufsort64",
|
||||||
|
"liblog",
|
||||||
|
"libotautil",
|
||||||
|
"libutils",
|
||||||
|
"libz_stable",
|
||||||
|
"libziparchive",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_binary_host {
|
||||||
|
name: "imgdiff",
|
||||||
|
srcs: [
|
||||||
|
"imgdiff_main.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"applypatch_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libimgdiff",
|
||||||
|
"libotautil",
|
||||||
|
"libbsdiff",
|
||||||
|
"libdivsufsort",
|
||||||
|
"libdivsufsort64",
|
||||||
|
"libziparchive",
|
||||||
|
"libbase",
|
||||||
|
"libutils",
|
||||||
|
"liblog",
|
||||||
|
"libbrotli",
|
||||||
|
"libbz",
|
||||||
|
"libz_stable",
|
||||||
|
],
|
||||||
|
}
|
41
applypatch/NOTICE
Normal file
41
applypatch/NOTICE
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
Copyright (C) 2009 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.
|
||||||
|
|
||||||
|
|
||||||
|
bsdiff.c
|
||||||
|
bspatch.c
|
||||||
|
|
||||||
|
Copyright 2003-2005 Colin Percival
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted providing that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
457
applypatch/applypatch.cpp
Normal file
457
applypatch/applypatch.cpp
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2008 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 "applypatch/applypatch.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
static bool GenerateTarget(const Partition& target, const FileContents& source_file,
|
||||||
|
const Value& patch, const Value* bonus_data, bool backup_source);
|
||||||
|
|
||||||
|
bool LoadFileContents(const std::string& filename, FileContents* file) {
|
||||||
|
// No longer allow loading contents from eMMC partitions.
|
||||||
|
if (android::base::StartsWith(filename, "EMMC:")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
if (!android::base::ReadFileToString(filename, &data)) {
|
||||||
|
PLOG(ERROR) << "Failed to read \"" << filename << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file->data = std::vector<unsigned char>(data.begin(), data.end());
|
||||||
|
SHA1(file->data.data(), file->data.size(), file->sha1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the contents of a Partition to the given FileContents buffer.
|
||||||
|
static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out,
|
||||||
|
bool check_backup) {
|
||||||
|
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
|
||||||
|
if (ParseSha1(partition.hash, expected_sha1) != 0) {
|
||||||
|
LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY));
|
||||||
|
if (dev == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\"";
|
||||||
|
} else {
|
||||||
|
std::vector<unsigned char> buffer(partition.size);
|
||||||
|
if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) {
|
||||||
|
PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition "
|
||||||
|
<< partition;
|
||||||
|
} else {
|
||||||
|
SHA1(buffer.data(), buffer.size(), out->sha1);
|
||||||
|
if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
|
||||||
|
out->data = std::move(buffer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_backup) {
|
||||||
|
LOG(ERROR) << "Partition contents don't have the expected checksum";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadFileContents(Paths::Get().cache_temp_source(), out) &&
|
||||||
|
memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveFileContents(const std::string& filename, const FileContents* file) {
|
||||||
|
android::base::unique_fd fd(
|
||||||
|
open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR));
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to open \"" << filename << "\" for write";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!android::base::WriteFully(fd, file->data.data(), file->data.size())) {
|
||||||
|
PLOG(ERROR) << "Failed to write " << file->data.size() << " bytes of data to " << filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fsync(fd) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to fsync \"" << filename << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(fd.release()) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to close \"" << filename << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a memory buffer to 'target' Partition.
|
||||||
|
static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) {
|
||||||
|
const unsigned char* data = file_contents.data.data();
|
||||||
|
size_t len = file_contents.data.size();
|
||||||
|
size_t start = 0;
|
||||||
|
bool success = false;
|
||||||
|
for (size_t attempt = 0; attempt < 2; ++attempt) {
|
||||||
|
android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR));
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to open \"" << partition << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!android::base::WriteFully(fd, data + start, len - start)) {
|
||||||
|
PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fsync(fd) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to sync \"" << partition << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (close(fd.release()) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to close \"" << partition << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd.reset(open(partition.name.c_str(), O_RDONLY));
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop caches so our subsequent verification read won't just be reading the cache.
|
||||||
|
sync();
|
||||||
|
std::string drop_cache = "/proc/sys/vm/drop_caches";
|
||||||
|
if (!android::base::WriteStringToFile("3\n", drop_cache)) {
|
||||||
|
PLOG(ERROR) << "Failed to write to " << drop_cache;
|
||||||
|
} else {
|
||||||
|
LOG(INFO) << " caches dropped";
|
||||||
|
}
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
// Verify.
|
||||||
|
if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to seek to 0 on " << partition;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char buffer[4096];
|
||||||
|
start = len;
|
||||||
|
for (size_t p = 0; p < len; p += sizeof(buffer)) {
|
||||||
|
size_t to_read = len - p;
|
||||||
|
if (to_read > sizeof(buffer)) {
|
||||||
|
to_read = sizeof(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!android::base::ReadFully(fd, buffer, to_read)) {
|
||||||
|
PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(buffer, data + p, to_read) != 0) {
|
||||||
|
LOG(ERROR) << "Verification failed starting at " << p;
|
||||||
|
start = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start == len) {
|
||||||
|
LOG(INFO) << "Verification read succeeded (attempt " << attempt + 1 << ")";
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(fd.release()) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to close " << partition;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
LOG(ERROR) << "Failed to verify after all attempts";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ParseSha1(const std::string& str, uint8_t* digest) {
|
||||||
|
const char* ps = str.c_str();
|
||||||
|
uint8_t* pd = digest;
|
||||||
|
for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) {
|
||||||
|
int digit;
|
||||||
|
if (*ps >= '0' && *ps <= '9') {
|
||||||
|
digit = *ps - '0';
|
||||||
|
} else if (*ps >= 'a' && *ps <= 'f') {
|
||||||
|
digit = *ps - 'a' + 10;
|
||||||
|
} else if (*ps >= 'A' && *ps <= 'F') {
|
||||||
|
digit = *ps - 'A' + 10;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
*pd = digit << 4;
|
||||||
|
} else {
|
||||||
|
*pd |= digit;
|
||||||
|
++pd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*ps != '\0') return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PatchPartitionCheck(const Partition& target, const Partition& source) {
|
||||||
|
FileContents target_file;
|
||||||
|
FileContents source_file;
|
||||||
|
return (ReadPartitionToBuffer(target, &target_file, false) ||
|
||||||
|
ReadPartitionToBuffer(source, &source_file, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ShowLicenses() {
|
||||||
|
ShowBSDiffLicense();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
|
||||||
|
const Value* bonus, bool backup_source) {
|
||||||
|
LOG(INFO) << "Patching " << target.name;
|
||||||
|
|
||||||
|
// We try to load and check against the target hash first.
|
||||||
|
FileContents target_file;
|
||||||
|
if (ReadPartitionToBuffer(target, &target_file, false)) {
|
||||||
|
// The early-exit case: the patch was already applied, this file has the desired hash, nothing
|
||||||
|
// for us to do.
|
||||||
|
LOG(INFO) << " already " << target.hash.substr(0, 8);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileContents source_file;
|
||||||
|
if (ReadPartitionToBuffer(source, &source_file, backup_source)) {
|
||||||
|
return GenerateTarget(target, source_file, patch, bonus, backup_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR) << "Failed to find any match";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlashPartition(const Partition& partition, const std::string& source_filename) {
|
||||||
|
LOG(INFO) << "Flashing " << partition;
|
||||||
|
|
||||||
|
// We try to load and check against the target hash first.
|
||||||
|
FileContents target_file;
|
||||||
|
if (ReadPartitionToBuffer(partition, &target_file, false)) {
|
||||||
|
// The early-exit case: the patch was already applied, this file has the desired hash, nothing
|
||||||
|
// for us to do.
|
||||||
|
LOG(INFO) << " already " << partition.hash.substr(0, 8);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileContents source_file;
|
||||||
|
if (!LoadFileContents(source_filename, &source_file)) {
|
||||||
|
LOG(ERROR) << "Failed to load source file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
|
||||||
|
if (ParseSha1(partition.hash, expected_sha1) != 0) {
|
||||||
|
LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
|
||||||
|
// The source doesn't have desired checksum.
|
||||||
|
LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum";
|
||||||
|
LOG(ERROR) << "expected: " << partition.hash.substr(0, 8)
|
||||||
|
<< ", found: " << short_sha1(source_file.sha1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!WriteBufferToPartition(source_file, partition)) {
|
||||||
|
LOG(ERROR) << "Failed to write to " << partition;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool GenerateTarget(const Partition& target, const FileContents& source_file,
|
||||||
|
const Value& patch, const Value* bonus_data, bool backup_source) {
|
||||||
|
uint8_t expected_sha1[SHA_DIGEST_LENGTH];
|
||||||
|
if (ParseSha1(target.hash, expected_sha1) != 0) {
|
||||||
|
LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patch.type != Value::Type::BLOB) {
|
||||||
|
LOG(ERROR) << "patch is not a blob";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* header = patch.data.data();
|
||||||
|
size_t header_bytes_read = patch.data.size();
|
||||||
|
bool use_bsdiff = false;
|
||||||
|
if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) {
|
||||||
|
use_bsdiff = true;
|
||||||
|
} else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) {
|
||||||
|
use_bsdiff = false;
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Unknown patch file format";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We write the original source to cache, in case the partition write is interrupted.
|
||||||
|
if (backup_source && !CheckAndFreeSpaceOnCache(source_file.data.size())) {
|
||||||
|
LOG(ERROR) << "Not enough free space on /cache";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (backup_source && !SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) {
|
||||||
|
LOG(ERROR) << "Failed to back up source file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We store the decoded output in memory.
|
||||||
|
FileContents patched;
|
||||||
|
SHA_CTX ctx;
|
||||||
|
SHA1_Init(&ctx);
|
||||||
|
SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) {
|
||||||
|
SHA1_Update(&ctx, data, len);
|
||||||
|
patched.data.insert(patched.data.end(), data, data + len);
|
||||||
|
return len;
|
||||||
|
};
|
||||||
|
|
||||||
|
int result;
|
||||||
|
if (use_bsdiff) {
|
||||||
|
result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink);
|
||||||
|
} else {
|
||||||
|
result =
|
||||||
|
ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != 0) {
|
||||||
|
LOG(ERROR) << "Failed to apply the patch: " << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHA1_Final(patched.sha1, &ctx);
|
||||||
|
if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) {
|
||||||
|
LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1);
|
||||||
|
|
||||||
|
LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1);
|
||||||
|
LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 "
|
||||||
|
<< short_sha1(source_file.sha1);
|
||||||
|
|
||||||
|
uint8_t patch_digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(reinterpret_cast<const uint8_t*>(patch.data.data()), patch.data.size(), patch_digest);
|
||||||
|
LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest);
|
||||||
|
|
||||||
|
if (bonus_data != nullptr) {
|
||||||
|
uint8_t bonus_digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(reinterpret_cast<const uint8_t*>(bonus_data->data.data()), bonus_data->data.size(),
|
||||||
|
bonus_digest);
|
||||||
|
LOG(ERROR) << "bonus size " << bonus_data->data.size() << " SHA-1 "
|
||||||
|
<< short_sha1(bonus_digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << " now " << short_sha1(expected_sha1);
|
||||||
|
|
||||||
|
// Write back the temp file to the partition.
|
||||||
|
if (!WriteBufferToPartition(patched, target)) {
|
||||||
|
LOG(ERROR) << "Failed to write patched data to " << target.name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the backup copy of the source.
|
||||||
|
if (backup_source) {
|
||||||
|
unlink(Paths::Get().cache_temp_source().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckPartition(const Partition& partition) {
|
||||||
|
FileContents target_file;
|
||||||
|
return ReadPartitionToBuffer(partition, &target_file, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Partition Partition::Parse(const std::string& input_str, std::string* err) {
|
||||||
|
std::vector<std::string> pieces = android::base::Split(input_str, ":");
|
||||||
|
if (pieces.size() != 4 || pieces[0] != "EMMC") {
|
||||||
|
*err = "Invalid number of tokens or non-eMMC target";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size;
|
||||||
|
if (!android::base::ParseUint(pieces[2], &size) || size == 0) {
|
||||||
|
*err = "Failed to parse \"" + pieces[2] + "\" as byte count";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Partition(pieces[1], size, pieces[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Partition::ToString() const {
|
||||||
|
if (*this) {
|
||||||
|
return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash;
|
||||||
|
}
|
||||||
|
return "<invalid-partition>";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const Partition& partition) {
|
||||||
|
os << partition.ToString();
|
||||||
|
return os;
|
||||||
|
}
|
25
applypatch/applypatch_main.cpp
Normal file
25
applypatch/applypatch_main.cpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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 "applypatch_modes.h"
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
|
||||||
|
// See the comments for applypatch() function.
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
android::base::InitLogging(argv);
|
||||||
|
return applypatch_modes(argc, argv);
|
||||||
|
}
|
187
applypatch/applypatch_modes.cpp
Normal file
187
applypatch/applypatch_modes.cpp
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 "applypatch_modes.h"
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "applypatch/applypatch.h"
|
||||||
|
#include "edify/expr.h"
|
||||||
|
|
||||||
|
static int CheckMode(const std::string& target_emmc) {
|
||||||
|
std::string err;
|
||||||
|
auto target = Partition::Parse(target_emmc, &err);
|
||||||
|
if (!target) {
|
||||||
|
LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return CheckPartition(target) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FlashMode(const std::string& target_emmc, const std::string& source_file) {
|
||||||
|
std::string err;
|
||||||
|
auto target = Partition::Parse(target_emmc, &err);
|
||||||
|
if (!target) {
|
||||||
|
LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return FlashPartition(target, source_file) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int PatchMode(const std::string& target_emmc, const std::string& source_emmc,
|
||||||
|
const std::string& patch_file, const std::string& bonus_file) {
|
||||||
|
std::string err;
|
||||||
|
auto target = Partition::Parse(target_emmc, &err);
|
||||||
|
if (!target) {
|
||||||
|
LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source = Partition::Parse(source_emmc, &err);
|
||||||
|
if (!source) {
|
||||||
|
LOG(ERROR) << "Failed to parse source \"" << source_emmc << "\": " << err;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string patch_contents;
|
||||||
|
if (!android::base::ReadFileToString(patch_file, &patch_contents)) {
|
||||||
|
PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\"";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value patch(Value::Type::BLOB, std::move(patch_contents));
|
||||||
|
std::unique_ptr<Value> bonus;
|
||||||
|
if (!bonus_file.empty()) {
|
||||||
|
std::string bonus_contents;
|
||||||
|
if (!android::base::ReadFileToString(bonus_file, &bonus_contents)) {
|
||||||
|
PLOG(ERROR) << "Failed to read bonus file \"" << bonus_file << "\"";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
bonus = std::make_unique<Value>(Value::Type::BLOB, std::move(bonus_contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatchPartition(target, source, patch, bonus.get(), false) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Usage() {
|
||||||
|
printf(
|
||||||
|
"Usage: \n"
|
||||||
|
"check mode\n"
|
||||||
|
" applypatch --check EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
|
||||||
|
"flash mode\n"
|
||||||
|
" applypatch --flash <source-file>\n"
|
||||||
|
" --target EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
|
||||||
|
"patch mode\n"
|
||||||
|
" applypatch [--bonus <bonus-file>]\n"
|
||||||
|
" --patch <patch-file>\n"
|
||||||
|
" --target EMMC:<target-file>:<target-size>:<target-sha1>\n"
|
||||||
|
" --source EMMC:<source-file>:<source-size>:<source-sha1>\n\n"
|
||||||
|
"show license\n"
|
||||||
|
" applypatch --license\n"
|
||||||
|
"\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int applypatch_modes(int argc, char* argv[]) {
|
||||||
|
static constexpr struct option OPTIONS[]{
|
||||||
|
// clang-format off
|
||||||
|
{ "bonus", required_argument, nullptr, 0 },
|
||||||
|
{ "check", required_argument, nullptr, 0 },
|
||||||
|
{ "flash", required_argument, nullptr, 0 },
|
||||||
|
{ "license", no_argument, nullptr, 0 },
|
||||||
|
{ "patch", required_argument, nullptr, 0 },
|
||||||
|
{ "source", required_argument, nullptr, 0 },
|
||||||
|
{ "target", required_argument, nullptr, 0 },
|
||||||
|
{ nullptr, 0, nullptr, 0 },
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string check_target;
|
||||||
|
std::string source;
|
||||||
|
std::string target;
|
||||||
|
std::string patch;
|
||||||
|
std::string bonus;
|
||||||
|
|
||||||
|
bool check_mode = false;
|
||||||
|
bool flash_mode = false;
|
||||||
|
bool patch_mode = false;
|
||||||
|
|
||||||
|
optind = 1;
|
||||||
|
|
||||||
|
int arg;
|
||||||
|
int option_index;
|
||||||
|
while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
|
||||||
|
switch (arg) {
|
||||||
|
case 0: {
|
||||||
|
std::string option = OPTIONS[option_index].name;
|
||||||
|
if (option == "bonus") {
|
||||||
|
bonus = optarg;
|
||||||
|
} else if (option == "check") {
|
||||||
|
check_target = optarg;
|
||||||
|
check_mode = true;
|
||||||
|
} else if (option == "flash") {
|
||||||
|
source = optarg;
|
||||||
|
flash_mode = true;
|
||||||
|
} else if (option == "license") {
|
||||||
|
return ShowLicenses();
|
||||||
|
} else if (option == "patch") {
|
||||||
|
patch = optarg;
|
||||||
|
patch_mode = true;
|
||||||
|
} else if (option == "source") {
|
||||||
|
source = optarg;
|
||||||
|
} else if (option == "target") {
|
||||||
|
target = optarg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '?':
|
||||||
|
default:
|
||||||
|
LOG(ERROR) << "Invalid argument";
|
||||||
|
Usage();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_mode) {
|
||||||
|
return CheckMode(check_target);
|
||||||
|
}
|
||||||
|
if (flash_mode) {
|
||||||
|
if (!bonus.empty()) {
|
||||||
|
LOG(ERROR) << "bonus file not supported in flash mode";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return FlashMode(target, source);
|
||||||
|
}
|
||||||
|
if (patch_mode) {
|
||||||
|
return PatchMode(target, source, patch, bonus);
|
||||||
|
}
|
||||||
|
|
||||||
|
Usage();
|
||||||
|
return 2;
|
||||||
|
}
|
22
applypatch/applypatch_modes.h
Normal file
22
applypatch/applypatch_modes.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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 _APPLYPATCH_MODES_H
|
||||||
|
#define _APPLYPATCH_MODES_H
|
||||||
|
|
||||||
|
int applypatch_modes(int argc, char* argv[]);
|
||||||
|
|
||||||
|
#endif // _APPLYPATCH_MODES_H
|
87
applypatch/bspatch.cpp
Normal file
87
applypatch/bspatch.cpp
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2008 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is a nearly line-for-line copy of bspatch.c from the
|
||||||
|
// bsdiff-4.3 distribution; the primary differences being how the
|
||||||
|
// input and output data are read and the error handling. Running
|
||||||
|
// applypatch with the -l option will display the bsdiff license
|
||||||
|
// notice.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <bsdiff/bspatch.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "applypatch/applypatch.h"
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
|
||||||
|
void ShowBSDiffLicense() {
|
||||||
|
puts("The bsdiff library used herein is:\n"
|
||||||
|
"\n"
|
||||||
|
"Copyright 2003-2005 Colin Percival\n"
|
||||||
|
"All rights reserved\n"
|
||||||
|
"\n"
|
||||||
|
"Redistribution and use in source and binary forms, with or without\n"
|
||||||
|
"modification, are permitted providing that the following conditions\n"
|
||||||
|
"are met:\n"
|
||||||
|
"1. Redistributions of source code must retain the above copyright\n"
|
||||||
|
" notice, this list of conditions and the following disclaimer.\n"
|
||||||
|
"2. Redistributions in binary form must reproduce the above copyright\n"
|
||||||
|
" notice, this list of conditions and the following disclaimer in the\n"
|
||||||
|
" documentation and/or other materials provided with the distribution.\n"
|
||||||
|
"\n"
|
||||||
|
"THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
|
||||||
|
"IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"
|
||||||
|
"WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
|
||||||
|
"ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n"
|
||||||
|
"DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n"
|
||||||
|
"DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n"
|
||||||
|
"OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
|
||||||
|
"HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
|
||||||
|
"STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
|
||||||
|
"IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
|
||||||
|
"POSSIBILITY OF SUCH DAMAGE.\n"
|
||||||
|
"\n------------------\n\n"
|
||||||
|
"This program uses Julian R Seward's \"libbzip2\" library, available\n"
|
||||||
|
"from http://www.bzip.org/.\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
|
||||||
|
size_t patch_offset, SinkFn sink) {
|
||||||
|
CHECK_LE(patch_offset, patch.data.size());
|
||||||
|
|
||||||
|
int result = bsdiff::bspatch(old_data, old_size,
|
||||||
|
reinterpret_cast<const uint8_t*>(&patch.data[patch_offset]),
|
||||||
|
patch.data.size() - patch_offset, sink);
|
||||||
|
if (result != 0) {
|
||||||
|
LOG(ERROR) << "bspatch failed, result: " << result;
|
||||||
|
// print SHA1 of the patch in the case of a data error.
|
||||||
|
if (result == 2) {
|
||||||
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(reinterpret_cast<const uint8_t*>(patch.data.data() + patch_offset),
|
||||||
|
patch.data.size() - patch_offset, digest);
|
||||||
|
std::string patch_sha1 = print_sha1(digest);
|
||||||
|
LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " << patch_sha1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
247
applypatch/freecache.cpp
Normal file
247
applypatch/freecache.cpp
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2010 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 <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/statfs.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
|
||||||
|
#include "applypatch/applypatch.h"
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
|
||||||
|
static int EliminateOpenFiles(const std::string& dirname, std::set<std::string>* files) {
|
||||||
|
std::unique_ptr<DIR, decltype(&closedir)> d(opendir("/proc"), closedir);
|
||||||
|
if (!d) {
|
||||||
|
PLOG(ERROR) << "Failed to open /proc";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
struct dirent* de;
|
||||||
|
while ((de = readdir(d.get())) != 0) {
|
||||||
|
unsigned int pid;
|
||||||
|
if (!android::base::ParseUint(de->d_name, &pid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string path = android::base::StringPrintf("/proc/%s/fd/", de->d_name);
|
||||||
|
|
||||||
|
struct dirent* fdde;
|
||||||
|
std::unique_ptr<DIR, decltype(&closedir)> fdd(opendir(path.c_str()), closedir);
|
||||||
|
if (!fdd) {
|
||||||
|
PLOG(ERROR) << "Failed to open " << path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while ((fdde = readdir(fdd.get())) != 0) {
|
||||||
|
std::string fd_path = path + fdde->d_name;
|
||||||
|
char link[FILENAME_MAX];
|
||||||
|
|
||||||
|
int count = readlink(fd_path.c_str(), link, sizeof(link)-1);
|
||||||
|
if (count >= 0) {
|
||||||
|
link[count] = '\0';
|
||||||
|
if (android::base::StartsWith(link, dirname)) {
|
||||||
|
if (files->erase(link) > 0) {
|
||||||
|
LOG(INFO) << link << " is open by " << de->d_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> FindExpendableFiles(
|
||||||
|
const std::string& dirname, const std::function<bool(const std::string&)>& name_filter) {
|
||||||
|
std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
|
||||||
|
if (!d) {
|
||||||
|
PLOG(ERROR) << "Failed to open " << dirname;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for regular files in the directory (not in any subdirectories).
|
||||||
|
std::set<std::string> files;
|
||||||
|
struct dirent* de;
|
||||||
|
while ((de = readdir(d.get())) != 0) {
|
||||||
|
std::string path = dirname + "/" + de->d_name;
|
||||||
|
|
||||||
|
// We can't delete cache_temp_source; if it's there we might have restarted during
|
||||||
|
// installation and could be depending on it to be there.
|
||||||
|
if (path == Paths::Get().cache_temp_source()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not delete the file if it doesn't have the expected format.
|
||||||
|
if (name_filter != nullptr && !name_filter(de->d_name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
|
||||||
|
files.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << files.size() << " regular files in deletable directory";
|
||||||
|
if (EliminateOpenFiles(dirname, &files) < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<std::string>(files.begin(), files.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the index of given log file, e.g. 3 for last_log.3; returns max number if the log name
|
||||||
|
// doesn't have the expected format so that we'll delete these ones first.
|
||||||
|
static unsigned int GetLogIndex(const std::string& log_name) {
|
||||||
|
if (log_name == "last_log" || log_name == "last_kmsg") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int index;
|
||||||
|
if (sscanf(log_name.c_str(), "last_log.%u", &index) == 1 ||
|
||||||
|
sscanf(log_name.c_str(), "last_kmsg.%u", &index) == 1) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::numeric_limits<unsigned int>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the amount of free space (in bytes) on the filesystem containing filename, or -1 on
|
||||||
|
// error.
|
||||||
|
static int64_t FreeSpaceForFile(const std::string& filename) {
|
||||||
|
struct statfs sf;
|
||||||
|
if (statfs(filename.c_str(), &sf) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to statfs " << filename;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto f_bsize = static_cast<int64_t>(sf.f_bsize);
|
||||||
|
auto free_space = sf.f_bsize * sf.f_bavail;
|
||||||
|
if (f_bsize == 0 || free_space / f_bsize != static_cast<int64_t>(sf.f_bavail)) {
|
||||||
|
LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail "
|
||||||
|
<< sf.f_bavail << ")";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return free_space;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckAndFreeSpaceOnCache(size_t bytes) {
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
// TODO(xunchang): Implement a heuristic cache size check during host simulation.
|
||||||
|
LOG(WARNING) << "Skipped making (" << bytes
|
||||||
|
<< ") bytes free space on /cache; program is running on host";
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<std::string> dirs{ "/cache", Paths::Get().cache_log_directory() };
|
||||||
|
for (const auto& dirname : dirs) {
|
||||||
|
if (RemoveFilesInDirectory(bytes, dirname, FreeSpaceForFile)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
|
||||||
|
const std::function<int64_t(const std::string&)>& space_checker) {
|
||||||
|
// The requested size cannot exceed max int64_t.
|
||||||
|
if (static_cast<uint64_t>(bytes_needed) >
|
||||||
|
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
|
||||||
|
LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(dirname.c_str(), &st) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to stat " << dirname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!S_ISDIR(st.st_mode)) {
|
||||||
|
LOG(ERROR) << dirname << " is not a directory";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t free_now = space_checker(dirname);
|
||||||
|
if (free_now == -1) {
|
||||||
|
LOG(ERROR) << "Failed to check free space for " << dirname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)";
|
||||||
|
|
||||||
|
if (free_now >= static_cast<int64_t>(bytes_needed)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> files;
|
||||||
|
if (dirname == Paths::Get().cache_log_directory()) {
|
||||||
|
// Deletes the log files only.
|
||||||
|
auto log_filter = [](const std::string& file_name) {
|
||||||
|
return android::base::StartsWith(file_name, "last_log") ||
|
||||||
|
android::base::StartsWith(file_name, "last_kmsg");
|
||||||
|
};
|
||||||
|
|
||||||
|
files = FindExpendableFiles(dirname, log_filter);
|
||||||
|
|
||||||
|
// Older logs will come to the top of the queue.
|
||||||
|
auto comparator = [](const std::string& name1, const std::string& name2) -> bool {
|
||||||
|
unsigned int index1 = GetLogIndex(android::base::Basename(name1));
|
||||||
|
unsigned int index2 = GetLogIndex(android::base::Basename(name2));
|
||||||
|
if (index1 == index2) {
|
||||||
|
return name1 < name2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index1 > index2;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::sort(files.begin(), files.end(), comparator);
|
||||||
|
} else {
|
||||||
|
// We're allowed to delete unopened regular files in the directory.
|
||||||
|
files = FindExpendableFiles(dirname, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& file : files) {
|
||||||
|
if (unlink(file.c_str()) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to delete " << file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
free_now = space_checker(dirname);
|
||||||
|
if (free_now == -1) {
|
||||||
|
LOG(ERROR) << "Failed to check free space for " << dirname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free";
|
||||||
|
if (free_now >= static_cast<int64_t>(bytes_needed)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
1641
applypatch/imgdiff.cpp
Normal file
1641
applypatch/imgdiff.cpp
Normal file
File diff suppressed because it is too large
Load diff
21
applypatch/imgdiff_main.cpp
Normal file
21
applypatch/imgdiff_main.cpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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 "applypatch/imgdiff.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
return imgdiff(argc, const_cast<const char**>(argv));
|
||||||
|
}
|
293
applypatch/imgpatch.cpp
Normal file
293
applypatch/imgpatch.cpp
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See imgdiff.cpp in this directory for a description of the patch file
|
||||||
|
// format.
|
||||||
|
|
||||||
|
#include <applypatch/imgpatch.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/memory.h>
|
||||||
|
#include <applypatch/applypatch.h>
|
||||||
|
#include <applypatch/imgdiff.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
|
||||||
|
static inline int64_t Read8(const void *address) {
|
||||||
|
return android::base::get_unaligned<int64_t>(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t Read4(const void *address) {
|
||||||
|
return android::base::get_unaligned<int32_t>(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the
|
||||||
|
// patched data and stream the deflated data to output.
|
||||||
|
static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len,
|
||||||
|
const Value& patch, size_t patch_offset,
|
||||||
|
const char* deflate_header, SinkFn sink) {
|
||||||
|
size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32));
|
||||||
|
CHECK_GT(expected_target_length, static_cast<size_t>(0));
|
||||||
|
int level = Read4(deflate_header + 40);
|
||||||
|
int method = Read4(deflate_header + 44);
|
||||||
|
int window_bits = Read4(deflate_header + 48);
|
||||||
|
int mem_level = Read4(deflate_header + 52);
|
||||||
|
int strategy = Read4(deflate_header + 56);
|
||||||
|
|
||||||
|
z_stream strm;
|
||||||
|
strm.zalloc = Z_NULL;
|
||||||
|
strm.zfree = Z_NULL;
|
||||||
|
strm.opaque = Z_NULL;
|
||||||
|
strm.avail_in = 0;
|
||||||
|
strm.next_in = nullptr;
|
||||||
|
int ret = deflateInit2(&strm, level, method, window_bits, mem_level, strategy);
|
||||||
|
if (ret != Z_OK) {
|
||||||
|
LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a custom sink wrapper that feeds to bspatch. It deflates the available patch data on
|
||||||
|
// the fly and outputs the compressed data to the given sink.
|
||||||
|
size_t actual_target_length = 0;
|
||||||
|
size_t total_written = 0;
|
||||||
|
static constexpr size_t buffer_size = 32768;
|
||||||
|
auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written,
|
||||||
|
&ret, &sink](const uint8_t* data, size_t len) -> size_t {
|
||||||
|
// The input patch length for an update never exceeds INT_MAX.
|
||||||
|
strm.avail_in = len;
|
||||||
|
strm.next_in = data;
|
||||||
|
do {
|
||||||
|
std::vector<uint8_t> buffer(buffer_size);
|
||||||
|
strm.avail_out = buffer_size;
|
||||||
|
strm.next_out = buffer.data();
|
||||||
|
if (actual_target_length + len < expected_target_length) {
|
||||||
|
ret = deflate(&strm, Z_NO_FLUSH);
|
||||||
|
} else {
|
||||||
|
ret = deflate(&strm, Z_FINISH);
|
||||||
|
}
|
||||||
|
if (ret != Z_OK && ret != Z_STREAM_END) {
|
||||||
|
LOG(ERROR) << "Failed to deflate stream: " << ret;
|
||||||
|
// zero length indicates an error in the sink function of bspatch().
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t have = buffer_size - strm.avail_out;
|
||||||
|
total_written += have;
|
||||||
|
if (sink(buffer.data(), have) != have) {
|
||||||
|
LOG(ERROR) << "Failed to write " << have << " compressed bytes to output.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END);
|
||||||
|
|
||||||
|
actual_target_length += len;
|
||||||
|
return len;
|
||||||
|
};
|
||||||
|
|
||||||
|
int bspatch_result = ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink);
|
||||||
|
deflateEnd(&strm);
|
||||||
|
|
||||||
|
if (bspatch_result != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != Z_STREAM_END) {
|
||||||
|
LOG(ERROR) << "ret is expected to be Z_STREAM_END, but it's " << ret;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected_target_length != actual_target_length) {
|
||||||
|
LOG(ERROR) << "target length is expected to be " << expected_target_length << ", but it's "
|
||||||
|
<< actual_target_length;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG(DEBUG) << "bspatch wrote " << total_written << " bytes in total to streaming output.";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
|
||||||
|
size_t patch_size, SinkFn sink) {
|
||||||
|
Value patch(Value::Type::BLOB,
|
||||||
|
std::string(reinterpret_cast<const char*>(patch_data), patch_size));
|
||||||
|
return ApplyImagePatch(old_data, old_size, patch, sink, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
|
||||||
|
const Value* bonus_data) {
|
||||||
|
if (patch.data.size() < 12) {
|
||||||
|
printf("patch too short to contain header\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. (IMGDIFF1, which is no longer
|
||||||
|
// supported, used CHUNK_NORMAL and CHUNK_GZIP.)
|
||||||
|
const char* const patch_header = patch.data.data();
|
||||||
|
if (memcmp(patch_header, "IMGDIFF2", 8) != 0) {
|
||||||
|
printf("corrupt patch file header (magic number)\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_chunks = Read4(patch_header + 8);
|
||||||
|
size_t pos = 12;
|
||||||
|
for (int i = 0; i < num_chunks; ++i) {
|
||||||
|
// each chunk's header record starts with 4 bytes.
|
||||||
|
if (pos + 4 > patch.data.size()) {
|
||||||
|
printf("failed to read chunk %d record\n", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int type = Read4(patch_header + pos);
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
if (type == CHUNK_NORMAL) {
|
||||||
|
const char* normal_header = patch_header + pos;
|
||||||
|
pos += 24;
|
||||||
|
if (pos > patch.data.size()) {
|
||||||
|
printf("failed to read chunk %d normal header data\n", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t src_start = static_cast<size_t>(Read8(normal_header));
|
||||||
|
size_t src_len = static_cast<size_t>(Read8(normal_header + 8));
|
||||||
|
size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16));
|
||||||
|
|
||||||
|
if (src_start + src_len > old_size) {
|
||||||
|
printf("source data too short\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink) != 0) {
|
||||||
|
printf("Failed to apply bsdiff patch.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Processed chunk type normal";
|
||||||
|
} else if (type == CHUNK_RAW) {
|
||||||
|
const char* raw_header = patch_header + pos;
|
||||||
|
pos += 4;
|
||||||
|
if (pos > patch.data.size()) {
|
||||||
|
printf("failed to read chunk %d raw header data\n", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t data_len = static_cast<size_t>(Read4(raw_header));
|
||||||
|
|
||||||
|
if (pos + data_len > patch.data.size()) {
|
||||||
|
printf("failed to read chunk %d raw data\n", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (sink(reinterpret_cast<const unsigned char*>(patch_header + pos), data_len) != data_len) {
|
||||||
|
printf("failed to write chunk %d raw data\n", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pos += data_len;
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Processed chunk type raw";
|
||||||
|
} else if (type == CHUNK_DEFLATE) {
|
||||||
|
// deflate chunks have an additional 60 bytes in their chunk header.
|
||||||
|
const char* deflate_header = patch_header + pos;
|
||||||
|
pos += 60;
|
||||||
|
if (pos > patch.data.size()) {
|
||||||
|
printf("failed to read chunk %d deflate header data\n", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t src_start = static_cast<size_t>(Read8(deflate_header));
|
||||||
|
size_t src_len = static_cast<size_t>(Read8(deflate_header + 8));
|
||||||
|
size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16));
|
||||||
|
size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24));
|
||||||
|
|
||||||
|
if (src_start + src_len > old_size) {
|
||||||
|
printf("source data too short\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress the source data; the chunk header tells us exactly
|
||||||
|
// how big we expect it to be when decompressed.
|
||||||
|
|
||||||
|
// Note: expanded_len will include the bonus data size if the patch was constructed with
|
||||||
|
// bonus data. The deflation will come up 'bonus_size' bytes short; these must be appended
|
||||||
|
// from the bonus_data value.
|
||||||
|
size_t bonus_size = (i == 1 && bonus_data != nullptr) ? bonus_data->data.size() : 0;
|
||||||
|
|
||||||
|
std::vector<unsigned char> expanded_source(expanded_len);
|
||||||
|
|
||||||
|
// inflate() doesn't like strm.next_out being a nullptr even with
|
||||||
|
// avail_out being zero (Z_STREAM_ERROR).
|
||||||
|
if (expanded_len != 0) {
|
||||||
|
z_stream strm;
|
||||||
|
strm.zalloc = Z_NULL;
|
||||||
|
strm.zfree = Z_NULL;
|
||||||
|
strm.opaque = Z_NULL;
|
||||||
|
strm.avail_in = src_len;
|
||||||
|
strm.next_in = old_data + src_start;
|
||||||
|
strm.avail_out = expanded_len;
|
||||||
|
strm.next_out = expanded_source.data();
|
||||||
|
|
||||||
|
int ret = inflateInit2(&strm, -15);
|
||||||
|
if (ret != Z_OK) {
|
||||||
|
printf("failed to init source inflation: %d\n", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because we've provided enough room to accommodate the output
|
||||||
|
// data, we expect one call to inflate() to suffice.
|
||||||
|
ret = inflate(&strm, Z_SYNC_FLUSH);
|
||||||
|
if (ret != Z_STREAM_END) {
|
||||||
|
printf("source inflation returned %d\n", ret);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// We should have filled the output buffer exactly, except
|
||||||
|
// for the bonus_size.
|
||||||
|
if (strm.avail_out != bonus_size) {
|
||||||
|
printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
inflateEnd(&strm);
|
||||||
|
|
||||||
|
if (bonus_size) {
|
||||||
|
memcpy(expanded_source.data() + (expanded_len - bonus_size), bonus_data->data.data(),
|
||||||
|
bonus_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch,
|
||||||
|
patch_offset, deflate_header, sink)) {
|
||||||
|
LOG(ERROR) << "Fail to apply streaming bspatch.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG) << "Processed chunk type deflate";
|
||||||
|
} else {
|
||||||
|
printf("patch chunk %d is unknown type %d\n", i, type);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
131
applypatch/include/applypatch/applypatch.h
Normal file
131
applypatch/include/applypatch/applypatch.h
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2008 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 _APPLYPATCH_H
|
||||||
|
#define _APPLYPATCH_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
// Forward declaration to avoid including "edify/expr.h" in the header.
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
struct FileContents {
|
||||||
|
uint8_t sha1[SHA_DIGEST_LENGTH];
|
||||||
|
std::vector<unsigned char> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
|
||||||
|
|
||||||
|
// applypatch.cpp
|
||||||
|
|
||||||
|
int ShowLicenses();
|
||||||
|
|
||||||
|
// Parses a given string of 40 hex digits into 20-byte array 'digest'. 'str' may contain only the
|
||||||
|
// digest or be of the form "<digest>:<anything>". Returns 0 on success, or -1 on any error.
|
||||||
|
int ParseSha1(const std::string& str, uint8_t* digest);
|
||||||
|
|
||||||
|
struct Partition {
|
||||||
|
Partition() = default;
|
||||||
|
|
||||||
|
Partition(const std::string& name, size_t size, const std::string& hash)
|
||||||
|
: name(name), size(size), hash(hash) {}
|
||||||
|
|
||||||
|
// Parses and returns the given string into a Partition object. The input string is of the form
|
||||||
|
// "EMMC:<device>:<size>:<hash>". Returns the parsed Partition, or an empty object on error.
|
||||||
|
static Partition Parse(const std::string& partition, std::string* err);
|
||||||
|
|
||||||
|
std::string ToString() const;
|
||||||
|
|
||||||
|
// Returns whether the current Partition object is valid.
|
||||||
|
explicit operator bool() const {
|
||||||
|
return !name.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
size_t size;
|
||||||
|
std::string hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const Partition& partition);
|
||||||
|
|
||||||
|
// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to
|
||||||
|
// the 'target' Partition. While patching, it will backup the data on the source partition to
|
||||||
|
// /cache, so that the patching could be resumed on interruption even if both of the source and
|
||||||
|
// target partitions refer to the same device. The function is idempotent if called multiple times.
|
||||||
|
// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr.
|
||||||
|
// 'backup_source' indicates whether the source partition should be backed up prior to the update
|
||||||
|
// (e.g. when doing in-place update). Returns the patching result.
|
||||||
|
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
|
||||||
|
const Value* bonus, bool backup_source);
|
||||||
|
|
||||||
|
// Returns whether the contents of the eMMC target or the cached file match the embedded hash.
|
||||||
|
// It will look for the backup on /cache if the given partition doesn't match the checksum.
|
||||||
|
bool PatchPartitionCheck(const Partition& target, const Partition& source);
|
||||||
|
|
||||||
|
// Checks whether the contents of the given partition has the desired hash. It will NOT look for
|
||||||
|
// the backup on /cache if the given partition doesn't have the expected checksum.
|
||||||
|
bool CheckPartition(const Partition& target);
|
||||||
|
|
||||||
|
// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target
|
||||||
|
// checksum first, and will return if target already has the desired hash. Otherwise it checks the
|
||||||
|
// checksum of the given source image, flashes, and verifies the target partition afterwards. The
|
||||||
|
// function is idempotent. Returns the flashing result.
|
||||||
|
bool FlashPartition(const Partition& target, const std::string& source_filename);
|
||||||
|
|
||||||
|
// Reads a file into memory; stores the file contents and associated metadata in *file.
|
||||||
|
bool LoadFileContents(const std::string& filename, FileContents* file);
|
||||||
|
|
||||||
|
// Saves the given FileContents object to the given filename.
|
||||||
|
bool SaveFileContents(const std::string& filename, const FileContents* file);
|
||||||
|
|
||||||
|
// bspatch.cpp
|
||||||
|
|
||||||
|
void ShowBSDiffLicense();
|
||||||
|
|
||||||
|
// Applies the bsdiff-patch given in 'patch' (from offset 'patch_offset' to the end) to the source
|
||||||
|
// data given by (old_data, old_size). Writes the patched output through the given 'sink'. Returns
|
||||||
|
// 0 on success.
|
||||||
|
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
|
||||||
|
size_t patch_offset, SinkFn sink);
|
||||||
|
|
||||||
|
// imgpatch.cpp
|
||||||
|
|
||||||
|
// Applies the imgdiff-patch given in 'patch' to the source data given by (old_data, old_size), with
|
||||||
|
// the optional bonus data. Writes the patched output through the given 'sink'. Returns 0 on
|
||||||
|
// success.
|
||||||
|
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
|
||||||
|
const Value* bonus_data);
|
||||||
|
|
||||||
|
// freecache.cpp
|
||||||
|
|
||||||
|
// Checks whether /cache partition has at least 'bytes'-byte free space. Returns true immediately
|
||||||
|
// if so. Otherwise, it will try to free some space by removing older logs, checks again and
|
||||||
|
// returns the checking result.
|
||||||
|
bool CheckAndFreeSpaceOnCache(size_t bytes);
|
||||||
|
|
||||||
|
// Removes the files in |dirname| until we have at least |bytes_needed| bytes of free space on the
|
||||||
|
// partition. |space_checker| should return the size of the free space, or -1 on error.
|
||||||
|
bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
|
||||||
|
const std::function<int64_t(const std::string&)>& space_checker);
|
||||||
|
#endif
|
39
applypatch/include/applypatch/imgdiff.h
Normal file
39
applypatch/include/applypatch/imgdiff.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 _APPLYPATCH_IMGDIFF_H
|
||||||
|
#define _APPLYPATCH_IMGDIFF_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Image patch chunk types
|
||||||
|
#define CHUNK_NORMAL 0
|
||||||
|
#define CHUNK_GZIP 1 // version 1 only
|
||||||
|
#define CHUNK_DEFLATE 2 // version 2 only
|
||||||
|
#define CHUNK_RAW 3 // version 2 only
|
||||||
|
|
||||||
|
// The gzip header size is actually variable, but we currently don't
|
||||||
|
// support gzipped data with any of the optional fields, so for now it
|
||||||
|
// will always be ten bytes. See RFC 1952 for the definition of the
|
||||||
|
// gzip format.
|
||||||
|
static constexpr size_t GZIP_HEADER_LEN = 10;
|
||||||
|
|
||||||
|
// The gzip footer size really is fixed.
|
||||||
|
static constexpr size_t GZIP_FOOTER_LEN = 8;
|
||||||
|
|
||||||
|
int imgdiff(int argc, const char** argv);
|
||||||
|
|
||||||
|
#endif // _APPLYPATCH_IMGDIFF_H
|
308
applypatch/include/applypatch/imgdiff_image.h
Normal file
308
applypatch/include/applypatch/imgdiff_image.h
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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 _APPLYPATCH_IMGDIFF_IMAGE_H
|
||||||
|
#define _APPLYPATCH_IMGDIFF_IMAGE_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <bsdiff/bsdiff.h>
|
||||||
|
#include <ziparchive/zip_archive.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
#include "imgdiff.h"
|
||||||
|
#include "otautil/rangeset.h"
|
||||||
|
|
||||||
|
class ImageChunk {
|
||||||
|
public:
|
||||||
|
static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream.
|
||||||
|
static constexpr auto MEMLEVEL = 8; // the default value.
|
||||||
|
static constexpr auto METHOD = Z_DEFLATED;
|
||||||
|
static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
|
||||||
|
|
||||||
|
ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len,
|
||||||
|
std::string entry_name = {});
|
||||||
|
|
||||||
|
int GetType() const {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* GetRawData() const;
|
||||||
|
size_t GetRawDataLength() const {
|
||||||
|
return raw_data_len_;
|
||||||
|
}
|
||||||
|
const std::string& GetEntryName() const {
|
||||||
|
return entry_name_;
|
||||||
|
}
|
||||||
|
size_t GetStartOffset() const {
|
||||||
|
return start_;
|
||||||
|
}
|
||||||
|
int GetCompressLevel() const {
|
||||||
|
return compress_level_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
|
||||||
|
// the raw data.
|
||||||
|
const uint8_t* DataForPatch() const;
|
||||||
|
size_t DataLengthForPatch() const;
|
||||||
|
|
||||||
|
void Dump(size_t index) const;
|
||||||
|
|
||||||
|
void SetUncompressedData(std::vector<uint8_t> data);
|
||||||
|
bool SetBonusData(const std::vector<uint8_t>& bonus_data);
|
||||||
|
|
||||||
|
bool operator==(const ImageChunk& other) const;
|
||||||
|
bool operator!=(const ImageChunk& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data).
|
||||||
|
* The resulting patch will likely be about as big as the target file, but it lets us handle
|
||||||
|
* the case of images where some gzip chunks are reconstructible but others aren't (by treating
|
||||||
|
* the ones that aren't as normal chunks).
|
||||||
|
*/
|
||||||
|
void ChangeDeflateChunkToNormal();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify that we can reproduce exactly the same compressed data that we started with. Sets the
|
||||||
|
* level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding
|
||||||
|
* parameters needed to produce the right output.
|
||||||
|
*/
|
||||||
|
bool ReconstructDeflateChunk();
|
||||||
|
bool IsAdjacentNormal(const ImageChunk& other) const;
|
||||||
|
void MergeAdjacentNormal(const ImageChunk& other);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data.
|
||||||
|
* |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used
|
||||||
|
* repeatedly, pass nullptr if not needed.
|
||||||
|
*/
|
||||||
|
static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
|
||||||
|
std::vector<uint8_t>* patch_data,
|
||||||
|
bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool TryReconstruction(int level);
|
||||||
|
|
||||||
|
int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
|
||||||
|
size_t start_; // offset of chunk in the original input file
|
||||||
|
const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
|
||||||
|
size_t raw_data_len_;
|
||||||
|
|
||||||
|
// deflate encoder parameters
|
||||||
|
int compress_level_;
|
||||||
|
|
||||||
|
// --- for CHUNK_DEFLATE chunks only: ---
|
||||||
|
std::vector<uint8_t> uncompressed_data_;
|
||||||
|
std::string entry_name_; // used for zip entries
|
||||||
|
};
|
||||||
|
|
||||||
|
// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track
|
||||||
|
// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length).
|
||||||
|
class PatchChunk {
|
||||||
|
public:
|
||||||
|
PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data);
|
||||||
|
|
||||||
|
// Construct a CHUNK_RAW patch from the target data directly.
|
||||||
|
explicit PatchChunk(const ImageChunk& tgt);
|
||||||
|
|
||||||
|
// Return true if raw data size is smaller than the patch size.
|
||||||
|
static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size);
|
||||||
|
|
||||||
|
// Update the source start with the new offset within the source range.
|
||||||
|
void UpdateSourceOffset(const SortedRangeSet& src_range);
|
||||||
|
|
||||||
|
// Return the total size (header + data) of the patch.
|
||||||
|
size_t PatchSize() const;
|
||||||
|
|
||||||
|
static bool WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t GetHeaderSize() const;
|
||||||
|
size_t WriteHeaderToFd(int fd, size_t offset, size_t index) const;
|
||||||
|
|
||||||
|
// The patch chunk type is the same as the target chunk type. The only exception is we change
|
||||||
|
// the |type_| to CHUNK_RAW if target length is smaller than the patch size.
|
||||||
|
int type_;
|
||||||
|
|
||||||
|
size_t source_start_;
|
||||||
|
size_t source_len_;
|
||||||
|
size_t source_uncompressed_len_;
|
||||||
|
|
||||||
|
size_t target_start_; // offset of the target chunk within the target file
|
||||||
|
size_t target_len_;
|
||||||
|
size_t target_uncompressed_len_;
|
||||||
|
size_t target_compress_level_; // the deflate compression level of the target chunk.
|
||||||
|
|
||||||
|
std::vector<uint8_t> data_; // storage for the patch data
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface for zip_mode and image_mode images. We initialize the image from an input file and
|
||||||
|
// split the file content into a list of image chunks.
|
||||||
|
class Image {
|
||||||
|
public:
|
||||||
|
explicit Image(bool is_source) : is_source_(is_source) {}
|
||||||
|
|
||||||
|
virtual ~Image() {}
|
||||||
|
|
||||||
|
// Create a list of image chunks from input file.
|
||||||
|
virtual bool Initialize(const std::string& filename) = 0;
|
||||||
|
|
||||||
|
// Look for runs of adjacent normal chunks and compress them down into a single chunk. (Such
|
||||||
|
// runs can be produced when deflate chunks are changed to normal chunks.)
|
||||||
|
void MergeAdjacentNormalChunks();
|
||||||
|
|
||||||
|
void DumpChunks() const;
|
||||||
|
|
||||||
|
// Non const iterators to access the stored ImageChunks.
|
||||||
|
std::vector<ImageChunk>::iterator begin() {
|
||||||
|
return chunks_.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ImageChunk>::iterator end() {
|
||||||
|
return chunks_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ImageChunk>::const_iterator cbegin() const {
|
||||||
|
return chunks_.cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ImageChunk>::const_iterator cend() const {
|
||||||
|
return chunks_.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageChunk& operator[](size_t i);
|
||||||
|
const ImageChunk& operator[](size_t i) const;
|
||||||
|
|
||||||
|
size_t NumOfChunks() const {
|
||||||
|
return chunks_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool ReadFile(const std::string& filename, std::vector<uint8_t>* file_content);
|
||||||
|
|
||||||
|
bool is_source_; // True if it's for source chunks.
|
||||||
|
std::vector<ImageChunk> chunks_; // Internal storage of ImageChunk.
|
||||||
|
std::vector<uint8_t> file_content_; // Store the whole input file in memory.
|
||||||
|
};
|
||||||
|
|
||||||
|
class ZipModeImage : public Image {
|
||||||
|
public:
|
||||||
|
explicit ZipModeImage(bool is_source, size_t limit = 0) : Image(is_source), limit_(limit) {}
|
||||||
|
|
||||||
|
bool Initialize(const std::string& filename) override;
|
||||||
|
|
||||||
|
// Initialize a fake ZipModeImage from an existing ImageChunk vector. For src img pieces, we
|
||||||
|
// reconstruct a new file_content based on the source ranges; but it's not needed for the tgt img
|
||||||
|
// pieces; because for each chunk both the data and their offset within the file are unchanged.
|
||||||
|
void Initialize(const std::vector<ImageChunk>& chunks, const std::vector<uint8_t>& file_content) {
|
||||||
|
chunks_ = chunks;
|
||||||
|
file_content_ = file_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in
|
||||||
|
// fact the whole source file.
|
||||||
|
ImageChunk PseudoSource() const;
|
||||||
|
|
||||||
|
// Find the matching deflate source chunk by entry name. Search for normal chunks also if
|
||||||
|
// |find_normal| is true.
|
||||||
|
ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false);
|
||||||
|
|
||||||
|
const ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false) const;
|
||||||
|
|
||||||
|
// Verify that we can reconstruct the deflate chunks; also change the type to CHUNK_NORMAL if
|
||||||
|
// src and tgt are identical.
|
||||||
|
static bool CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image);
|
||||||
|
|
||||||
|
// Compute the patch between tgt & src images, and write the data into |patch_name|.
|
||||||
|
static bool GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
|
||||||
|
const std::string& patch_name);
|
||||||
|
|
||||||
|
// Compute the patch based on the lists of split src and tgt images. Generate patches for each
|
||||||
|
// pair of split pieces and write the data to |patch_name|. If |debug_dir| is specified, write
|
||||||
|
// each split src data and patch data into that directory.
|
||||||
|
static bool GeneratePatches(const std::vector<ZipModeImage>& split_tgt_images,
|
||||||
|
const std::vector<ZipModeImage>& split_src_images,
|
||||||
|
const std::vector<SortedRangeSet>& split_src_ranges,
|
||||||
|
const std::string& patch_name, const std::string& split_info_file,
|
||||||
|
const std::string& debug_dir);
|
||||||
|
|
||||||
|
// Split the tgt chunks and src chunks based on the size limit.
|
||||||
|
static bool SplitZipModeImageWithLimit(const ZipModeImage& tgt_image,
|
||||||
|
const ZipModeImage& src_image,
|
||||||
|
std::vector<ZipModeImage>* split_tgt_images,
|
||||||
|
std::vector<ZipModeImage>* split_src_images,
|
||||||
|
std::vector<SortedRangeSet>* split_src_ranges);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Initialize image chunks based on the zip entries.
|
||||||
|
bool InitializeChunks(const std::string& filename, ZipArchiveHandle handle);
|
||||||
|
// Add the a zip entry to the list.
|
||||||
|
bool AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name,
|
||||||
|
ZipEntry64* entry);
|
||||||
|
// Return the real size of the zip file. (omit the trailing zeros that used for alignment)
|
||||||
|
bool GetZipFileSize(size_t* input_file_size);
|
||||||
|
|
||||||
|
static void ValidateSplitImages(const std::vector<ZipModeImage>& split_tgt_images,
|
||||||
|
const std::vector<ZipModeImage>& split_src_images,
|
||||||
|
std::vector<SortedRangeSet>& split_src_ranges,
|
||||||
|
size_t total_tgt_size);
|
||||||
|
// Construct the fake split images based on the chunks info and source ranges; and move them into
|
||||||
|
// the given vectors. Return true if we add a new split image into |split_tgt_images|, and
|
||||||
|
// false otherwise.
|
||||||
|
static bool AddSplitImageFromChunkList(const ZipModeImage& tgt_image,
|
||||||
|
const ZipModeImage& src_image,
|
||||||
|
const SortedRangeSet& split_src_ranges,
|
||||||
|
const std::vector<ImageChunk>& split_tgt_chunks,
|
||||||
|
const std::vector<ImageChunk>& split_src_chunks,
|
||||||
|
std::vector<ZipModeImage>* split_tgt_images,
|
||||||
|
std::vector<ZipModeImage>* split_src_images);
|
||||||
|
|
||||||
|
// Function that actually iterates the tgt_chunks and makes patches.
|
||||||
|
static bool GeneratePatchesInternal(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
|
||||||
|
std::vector<PatchChunk>* patch_chunks);
|
||||||
|
|
||||||
|
// size limit in bytes of each chunk. Also, if the length of one zip_entry exceeds the limit,
|
||||||
|
// we'll split that entry into several smaller chunks in advance.
|
||||||
|
size_t limit_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageModeImage : public Image {
|
||||||
|
public:
|
||||||
|
explicit ImageModeImage(bool is_source) : Image(is_source) {}
|
||||||
|
|
||||||
|
// Initialize the image chunks list by searching the magic numbers in an image file.
|
||||||
|
bool Initialize(const std::string& filename) override;
|
||||||
|
|
||||||
|
bool SetBonusData(const std::vector<uint8_t>& bonus_data);
|
||||||
|
|
||||||
|
// In Image Mode, verify that the source and target images have the same chunk structure (ie, the
|
||||||
|
// same sequence of deflate and normal chunks).
|
||||||
|
static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image);
|
||||||
|
|
||||||
|
// In image mode, generate patches against the given source chunks and bonus_data; write the
|
||||||
|
// result to |patch_name|.
|
||||||
|
static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image,
|
||||||
|
const std::string& patch_name);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _APPLYPATCH_IMGDIFF_IMAGE_H
|
29
applypatch/include/applypatch/imgpatch.h
Normal file
29
applypatch/include/applypatch/imgpatch.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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 _APPLYPATCH_IMGPATCH_H
|
||||||
|
#define _APPLYPATCH_IMGPATCH_H
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
|
||||||
|
|
||||||
|
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
|
||||||
|
size_t patch_size, SinkFn sink);
|
||||||
|
|
||||||
|
#endif // _APPLYPATCH_IMGPATCH_H
|
4
applypatch/vendor_flash_recovery.rc
Normal file
4
applypatch/vendor_flash_recovery.rc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
service vendor_flash_recovery /vendor/bin/install-recovery.sh
|
||||||
|
class main
|
||||||
|
oneshot
|
||||||
|
user root
|
56
edify/Android.bp
Normal file
56
edify/Android.bp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (C) 2017 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.
|
||||||
|
|
||||||
|
package {
|
||||||
|
// See: http://go/android-license-faq
|
||||||
|
// A large-scale-change added 'default_applicable_licenses' to import
|
||||||
|
// all of the 'license_kinds' from "bootable_recovery_license"
|
||||||
|
// to get the below license kinds:
|
||||||
|
// SPDX-license-identifier-Apache-2.0
|
||||||
|
default_applicable_licenses: ["bootable_recovery_license"],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libedify",
|
||||||
|
|
||||||
|
host_supported: true,
|
||||||
|
vendor_available: true,
|
||||||
|
recovery_available: true,
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"expr.cpp",
|
||||||
|
"lexer.ll",
|
||||||
|
"parser.yy",
|
||||||
|
],
|
||||||
|
|
||||||
|
cflags: [
|
||||||
|
"-Wall",
|
||||||
|
"-Werror",
|
||||||
|
"-Wno-deprecated-register",
|
||||||
|
"-Wno-unused-parameter",
|
||||||
|
],
|
||||||
|
|
||||||
|
export_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
|
||||||
|
local_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libbase",
|
||||||
|
"libotautil",
|
||||||
|
],
|
||||||
|
}
|
111
edify/README.md
Normal file
111
edify/README.md
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
edify
|
||||||
|
=====
|
||||||
|
|
||||||
|
Update scripts (from donut onwards) are written in a new little
|
||||||
|
scripting language ("edify") that is superficially somewhat similar to
|
||||||
|
the old one ("amend"). This is a brief overview of the new language.
|
||||||
|
|
||||||
|
- The entire script is a single expression.
|
||||||
|
|
||||||
|
- All expressions are string-valued.
|
||||||
|
|
||||||
|
- String literals appear in double quotes. \n, \t, \", and \\ are
|
||||||
|
understood, as are hexadecimal escapes like \x4a.
|
||||||
|
|
||||||
|
- String literals consisting of only letters, numbers, colons,
|
||||||
|
underscores, slashes, and periods don't need to be in double quotes.
|
||||||
|
|
||||||
|
- The following words are reserved:
|
||||||
|
|
||||||
|
if then else endif
|
||||||
|
|
||||||
|
They have special meaning when unquoted. (In quotes, they are just
|
||||||
|
string literals.)
|
||||||
|
|
||||||
|
- When used as a boolean, the empty string is "false" and all other
|
||||||
|
strings are "true".
|
||||||
|
|
||||||
|
- All functions are actually macros (in the Lisp sense); the body of
|
||||||
|
the function can control which (if any) of the arguments are
|
||||||
|
evaluated. This means that functions can act as control
|
||||||
|
structures.
|
||||||
|
|
||||||
|
- Operators (like "&&" and "||") are just syntactic sugar for builtin
|
||||||
|
functions, so they can act as control structures as well.
|
||||||
|
|
||||||
|
- ";" is a binary operator; evaluating it just means to first evaluate
|
||||||
|
the left side, then the right. It can also appear after any
|
||||||
|
expression.
|
||||||
|
|
||||||
|
- Comments start with "#" and run to the end of the line.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Some examples:
|
||||||
|
|
||||||
|
- There's no distinction between quoted and unquoted strings; the
|
||||||
|
quotes are only needed if you want characters like whitespace to
|
||||||
|
appear in the string. The following expressions all evaluate to the
|
||||||
|
same string.
|
||||||
|
|
||||||
|
"a b"
|
||||||
|
a + " " + b
|
||||||
|
"a" + " " + "b"
|
||||||
|
"a\x20b"
|
||||||
|
a + "\x20b"
|
||||||
|
concat(a, " ", "b")
|
||||||
|
"concat"(a, " ", "b")
|
||||||
|
|
||||||
|
As shown in the last example, function names are just strings,
|
||||||
|
too. They must be string *literals*, however. This is not legal:
|
||||||
|
|
||||||
|
("con" + "cat")(a, " ", b) # syntax error!
|
||||||
|
|
||||||
|
|
||||||
|
- The ifelse() builtin takes three arguments: it evaluates exactly
|
||||||
|
one of the second and third, depending on whether the first one is
|
||||||
|
true. There is also some syntactic sugar to make expressions that
|
||||||
|
look like if/else statements:
|
||||||
|
|
||||||
|
# these are all equivalent
|
||||||
|
ifelse(something(), "yes", "no")
|
||||||
|
if something() then yes else no endif
|
||||||
|
if something() then "yes" else "no" endif
|
||||||
|
|
||||||
|
The else part is optional.
|
||||||
|
|
||||||
|
if something() then "yes" endif # if something() is false,
|
||||||
|
# evaluates to false
|
||||||
|
|
||||||
|
ifelse(condition(), "", abort()) # abort() only called if
|
||||||
|
# condition() is false
|
||||||
|
|
||||||
|
The last example is equivalent to:
|
||||||
|
|
||||||
|
assert(condition())
|
||||||
|
|
||||||
|
|
||||||
|
- The && and || operators can be used similarly; they evaluate their
|
||||||
|
second argument only if it's needed to determine the truth of the
|
||||||
|
expression. Their value is the value of the last-evaluated
|
||||||
|
argument:
|
||||||
|
|
||||||
|
file_exists("/data/system/bad") && delete("/data/system/bad")
|
||||||
|
|
||||||
|
file_exists("/data/system/missing") || create("/data/system/missing")
|
||||||
|
|
||||||
|
get_it() || "xxx" # returns value of get_it() if that value is
|
||||||
|
# true, otherwise returns "xxx"
|
||||||
|
|
||||||
|
|
||||||
|
- The purpose of ";" is to simulate imperative statements, of course,
|
||||||
|
but the operator can be used anywhere. Its value is the value of
|
||||||
|
its right side:
|
||||||
|
|
||||||
|
concat(a;b;c, d, e;f) # evaluates to "cdf"
|
||||||
|
|
||||||
|
A more useful example might be something like:
|
||||||
|
|
||||||
|
ifelse(condition(),
|
||||||
|
(first_step(); second_step();), # second ; is optional
|
||||||
|
alternative_procedure())
|
425
edify/expr.cpp
Normal file
425
edify/expr.cpp
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 "edify/expr.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
|
||||||
|
#include "otautil/error_code.h"
|
||||||
|
|
||||||
|
// Functions should:
|
||||||
|
//
|
||||||
|
// - return a malloc()'d string
|
||||||
|
// - if Evaluate() on any argument returns nullptr, return nullptr.
|
||||||
|
|
||||||
|
static bool BooleanString(const std::string& s) {
|
||||||
|
return !s.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result) {
|
||||||
|
if (result == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Value> v(expr->fn(expr->name.c_str(), state, expr->argv));
|
||||||
|
if (!v) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (v->type != Value::Type::STRING) {
|
||||||
|
ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = v->data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr) {
|
||||||
|
return expr->fn(expr->name.c_str(), state, expr->argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* StringValue(const char* str) {
|
||||||
|
if (str == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return new Value(Value::Type::STRING, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* StringValue(const std::string& str) {
|
||||||
|
return StringValue(str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.empty()) {
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
std::string result;
|
||||||
|
for (size_t i = 0; i < argv.size(); ++i) {
|
||||||
|
std::string str;
|
||||||
|
if (!Evaluate(state, argv[i], &str)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
result += str;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2 && argv.size() != 3) {
|
||||||
|
state->errmsg = "ifelse expects 2 or 3 arguments";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cond;
|
||||||
|
if (!Evaluate(state, argv[0], &cond)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cond.empty()) {
|
||||||
|
return EvaluateValue(state, argv[1]);
|
||||||
|
} else if (argv.size() == 3) {
|
||||||
|
return EvaluateValue(state, argv[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string msg;
|
||||||
|
if (!argv.empty() && Evaluate(state, argv[0], &msg)) {
|
||||||
|
state->errmsg += msg;
|
||||||
|
} else {
|
||||||
|
state->errmsg += "called abort()";
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
for (size_t i = 0; i < argv.size(); ++i) {
|
||||||
|
std::string result;
|
||||||
|
if (!Evaluate(state, argv[i], &result)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (result.empty()) {
|
||||||
|
int len = argv[i]->end - argv[i]->start;
|
||||||
|
state->errmsg = "assert failed: " + state->script.substr(argv[i]->start, len);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* SleepFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string val;
|
||||||
|
if (!Evaluate(state, argv[0], &val)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int v;
|
||||||
|
if (!android::base::ParseInt(val.c_str(), &v, 0)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
sleep(v);
|
||||||
|
|
||||||
|
return StringValue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* StdoutFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
for (size_t i = 0; i < argv.size(); ++i) {
|
||||||
|
std::string v;
|
||||||
|
if (!Evaluate(state, argv[i], &v)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
fputs(v.c_str(), stdout);
|
||||||
|
}
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* LogicalAndFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string left;
|
||||||
|
if (!Evaluate(state, argv[0], &left)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (BooleanString(left)) {
|
||||||
|
return EvaluateValue(state, argv[1]);
|
||||||
|
} else {
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* LogicalOrFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string left;
|
||||||
|
if (!Evaluate(state, argv[0], &left)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!BooleanString(left)) {
|
||||||
|
return EvaluateValue(state, argv[1]);
|
||||||
|
} else {
|
||||||
|
return StringValue(left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* LogicalNotFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string val;
|
||||||
|
if (!Evaluate(state, argv[0], &val)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(BooleanString(val) ? "" : "t");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* SubstringFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string needle;
|
||||||
|
if (!Evaluate(state, argv[0], &needle)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string haystack;
|
||||||
|
if (!Evaluate(state, argv[1], &haystack)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result = (haystack.find(needle) != std::string::npos) ? "t" : "";
|
||||||
|
return StringValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string left;
|
||||||
|
if (!Evaluate(state, argv[0], &left)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::string right;
|
||||||
|
if (!Evaluate(state, argv[1], &right)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* result = (left == right) ? "t" : "";
|
||||||
|
return StringValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* InequalityFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::string left;
|
||||||
|
if (!Evaluate(state, argv[0], &left)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::string right;
|
||||||
|
if (!Evaluate(state, argv[1], &right)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* result = (left != right) ? "t" : "";
|
||||||
|
return StringValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::unique_ptr<Value> left(EvaluateValue(state, argv[0]));
|
||||||
|
if (!left) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return EvaluateValue(state, argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* LessThanIntFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
state->errmsg = "less_than_int expects 2 arguments";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse up to at least long long or 64-bit integers.
|
||||||
|
int64_t l_int;
|
||||||
|
if (!android::base::ParseInt(args[0].c_str(), &l_int)) {
|
||||||
|
state->errmsg = "failed to parse int in " + args[0];
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t r_int;
|
||||||
|
if (!android::base::ParseInt(args[1].c_str(), &r_int)) {
|
||||||
|
state->errmsg = "failed to parse int in " + args[1];
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(l_int < r_int ? "t" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* GreaterThanIntFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
state->errmsg = "greater_than_int expects 2 arguments";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse up to at least long long or 64-bit integers.
|
||||||
|
int64_t l_int;
|
||||||
|
if (!android::base::ParseInt(args[0].c_str(), &l_int)) {
|
||||||
|
state->errmsg = "failed to parse int in " + args[0];
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t r_int;
|
||||||
|
if (!android::base::ParseInt(args[1].c_str(), &r_int)) {
|
||||||
|
state->errmsg = "failed to parse int in " + args[1];
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(l_int > r_int ? "t" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
return StringValue(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// the function table
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, Function> fn_table;
|
||||||
|
|
||||||
|
void RegisterFunction(const std::string& name, Function fn) {
|
||||||
|
fn_table[name] = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function FindFunction(const std::string& name) {
|
||||||
|
if (fn_table.find(name) == fn_table.end()) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return fn_table[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterBuiltins() {
|
||||||
|
RegisterFunction("ifelse", IfElseFn);
|
||||||
|
RegisterFunction("abort", AbortFn);
|
||||||
|
RegisterFunction("assert", AssertFn);
|
||||||
|
RegisterFunction("concat", ConcatFn);
|
||||||
|
RegisterFunction("is_substring", SubstringFn);
|
||||||
|
RegisterFunction("stdout", StdoutFn);
|
||||||
|
RegisterFunction("sleep", SleepFn);
|
||||||
|
|
||||||
|
RegisterFunction("less_than_int", LessThanIntFn);
|
||||||
|
RegisterFunction("greater_than_int", GreaterThanIntFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// convenience methods for functions
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// Evaluate the expressions in argv, and put the results of strings in args. If any expression
|
||||||
|
// evaluates to nullptr, return false. Return true on success.
|
||||||
|
bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::string>* args) {
|
||||||
|
return ReadArgs(state, argv, args, 0, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::string>* args, size_t start, size_t len) {
|
||||||
|
if (args == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (start + len > argv.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = start; i < start + len; ++i) {
|
||||||
|
std::string var;
|
||||||
|
if (!Evaluate(state, argv[i], &var)) {
|
||||||
|
args->clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
args->push_back(var);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the expressions in argv, and put the results of Value* in args. If any expression
|
||||||
|
// evaluate to nullptr, return false. Return true on success.
|
||||||
|
bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::unique_ptr<Value>>* args) {
|
||||||
|
return ReadValueArgs(state, argv, args, 0, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len) {
|
||||||
|
if (args == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len == 0 || start + len > argv.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = start; i < start + len; ++i) {
|
||||||
|
std::unique_ptr<Value> v(EvaluateValue(state, argv[i]));
|
||||||
|
if (!v) {
|
||||||
|
args->clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
args->push_back(std::move(v));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use printf-style arguments to compose an error message to put into
|
||||||
|
// *state. Returns nullptr.
|
||||||
|
Value* ErrorAbort(State* state, const char* format, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, format);
|
||||||
|
android::base::StringAppendV(&state->errmsg, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) {
|
||||||
|
std::string err_message;
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, format);
|
||||||
|
android::base::StringAppendV(&err_message, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
// Ensure that there's exactly one line break at the end of the error message.
|
||||||
|
state->errmsg = android::base::Trim(err_message) + "\n";
|
||||||
|
state->cause_code = cause_code;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
State::State(const std::string& script, UpdaterInterface* interface)
|
||||||
|
: script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {}
|
159
edify/include/edify/expr.h
Normal file
159
edify/include/edify/expr.h
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 _EXPRESSION_H
|
||||||
|
#define _EXPRESSION_H
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "edify/updater_interface.h"
|
||||||
|
|
||||||
|
// Forward declaration to avoid including "otautil/error_code.h".
|
||||||
|
enum ErrorCode : int;
|
||||||
|
enum CauseCode : int;
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
State(const std::string& script, UpdaterInterface* cookie);
|
||||||
|
|
||||||
|
// The source of the original script.
|
||||||
|
const std::string& script;
|
||||||
|
|
||||||
|
// A pointer to app-specific data; the libedify doesn't use this value.
|
||||||
|
UpdaterInterface* updater;
|
||||||
|
|
||||||
|
// The error message (if any) returned if the evaluation aborts.
|
||||||
|
// Should be empty initially, will be either empty or a string that
|
||||||
|
// Evaluate() returns.
|
||||||
|
std::string errmsg;
|
||||||
|
|
||||||
|
// error code indicates the type of failure (e.g. failure to update system image)
|
||||||
|
// during the OTA process.
|
||||||
|
ErrorCode error_code;
|
||||||
|
|
||||||
|
// cause code provides more detailed reason of an OTA failure (e.g. fsync error)
|
||||||
|
// in addition to the error code.
|
||||||
|
CauseCode cause_code;
|
||||||
|
|
||||||
|
bool is_retry = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Value {
|
||||||
|
enum class Type {
|
||||||
|
STRING = 1,
|
||||||
|
BLOB = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
Value(Type type, std::string str) : type(type), data(std::move(str)) {}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
std::string data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Expr;
|
||||||
|
|
||||||
|
using Function = Value* (*)(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
|
||||||
|
struct Expr {
|
||||||
|
Function fn;
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::unique_ptr<Expr>> argv;
|
||||||
|
int start, end;
|
||||||
|
|
||||||
|
Expr(Function fn, const std::string& name, int start, int end) :
|
||||||
|
fn(fn),
|
||||||
|
name(name),
|
||||||
|
start(start),
|
||||||
|
end(end) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Evaluate the input expr, return the resulting Value.
|
||||||
|
Value* EvaluateValue(State* state, const std::unique_ptr<Expr>& expr);
|
||||||
|
|
||||||
|
// Evaluate the input expr, assert that it is a string, and update the result parameter. This
|
||||||
|
// function returns true if the evaluation succeeds. This is a convenience function for older
|
||||||
|
// functions that want to deal only with strings.
|
||||||
|
bool Evaluate(State* state, const std::unique_ptr<Expr>& expr, std::string* result);
|
||||||
|
|
||||||
|
// Glue to make an Expr out of a literal.
|
||||||
|
Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
|
||||||
|
// Functions corresponding to various syntactic sugar operators.
|
||||||
|
// ("concat" is also available as a builtin function, to concatenate
|
||||||
|
// more than two strings.)
|
||||||
|
Value* ConcatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* LogicalAndFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* LogicalOrFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* LogicalNotFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* SubstringFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* InequalityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
|
||||||
|
// Global builtins, registered by RegisterBuiltins().
|
||||||
|
Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
|
||||||
|
|
||||||
|
// Register a new function. The same Function may be registered under
|
||||||
|
// multiple names, but a given name should only be used once.
|
||||||
|
void RegisterFunction(const std::string& name, Function fn);
|
||||||
|
|
||||||
|
// Register all the builtins.
|
||||||
|
void RegisterBuiltins();
|
||||||
|
|
||||||
|
// Find the Function for a given name; return NULL if no such function
|
||||||
|
// exists.
|
||||||
|
Function FindFunction(const std::string& name);
|
||||||
|
|
||||||
|
// --- convenience functions for use in functions ---
|
||||||
|
|
||||||
|
// Evaluate the expressions in argv, and put the results of strings in args. If any expression
|
||||||
|
// evaluates to nullptr, return false. Return true on success.
|
||||||
|
bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::string>* args);
|
||||||
|
bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::string>* args, size_t start, size_t len);
|
||||||
|
|
||||||
|
// Evaluate the expressions in argv, and put the results of Value* in args. If any
|
||||||
|
// expression evaluate to nullptr, return false. Return true on success.
|
||||||
|
bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::unique_ptr<Value>>* args);
|
||||||
|
bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
std::vector<std::unique_ptr<Value>>* args, size_t start, size_t len);
|
||||||
|
|
||||||
|
// Use printf-style arguments to compose an error message to put into
|
||||||
|
// *state. Returns NULL.
|
||||||
|
Value* ErrorAbort(State* state, const char* format, ...)
|
||||||
|
__attribute__((format(printf, 2, 3), deprecated));
|
||||||
|
|
||||||
|
// ErrorAbort has an optional (but recommended) argument 'cause_code'. If the cause code
|
||||||
|
// is set, it will be logged into last_install and provides reason of OTA failures.
|
||||||
|
Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...)
|
||||||
|
__attribute__((format(printf, 3, 4)));
|
||||||
|
|
||||||
|
// Copying the string into a Value.
|
||||||
|
Value* StringValue(const char* str);
|
||||||
|
|
||||||
|
Value* StringValue(const std::string& str);
|
||||||
|
|
||||||
|
int ParseString(const std::string& str, std::unique_ptr<Expr>* root, int* error_count);
|
||||||
|
|
||||||
|
#endif // _EXPRESSION_H
|
48
edify/include/edify/updater_interface.h
Normal file
48
edify/include/edify/updater_interface.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
struct ZipArchive;
|
||||||
|
typedef ZipArchive* ZipArchiveHandle;
|
||||||
|
|
||||||
|
class UpdaterRuntimeInterface;
|
||||||
|
|
||||||
|
class UpdaterInterface {
|
||||||
|
public:
|
||||||
|
virtual ~UpdaterInterface() = default;
|
||||||
|
|
||||||
|
// Writes the message to command pipe, adds a new line in the end.
|
||||||
|
virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0;
|
||||||
|
|
||||||
|
// Sends over the message to recovery to print it on the screen.
|
||||||
|
virtual void UiPrint(const std::string_view message) const = 0;
|
||||||
|
|
||||||
|
// Given the name of the block device, returns |name| for updates on the device; or the file path
|
||||||
|
// to the fake block device for simulations.
|
||||||
|
virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
|
||||||
|
|
||||||
|
virtual UpdaterRuntimeInterface* GetRuntime() const = 0;
|
||||||
|
virtual ZipArchiveHandle GetPackageHandle() const = 0;
|
||||||
|
virtual std::string GetResult() const = 0;
|
||||||
|
virtual uint8_t* GetMappedPackageAddress() const = 0;
|
||||||
|
virtual size_t GetMappedPackageLength() const = 0;
|
||||||
|
};
|
77
edify/include/edify/updater_runtime_interface.h
Normal file
77
edify/include/edify/updater_runtime_interface.h
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and
|
||||||
|
// updates on device and host simulations can have different implementations. e.g. block devices
|
||||||
|
// during host simulation merely a temporary file. With this class, the caller side in registered
|
||||||
|
// updater's functions will stay the same for both update and simulation.
|
||||||
|
class UpdaterRuntimeInterface {
|
||||||
|
public:
|
||||||
|
virtual ~UpdaterRuntimeInterface() = default;
|
||||||
|
|
||||||
|
// Returns true if it's a runtime instance for simulation.
|
||||||
|
virtual bool IsSimulator() const = 0;
|
||||||
|
|
||||||
|
// Returns the value of system property |key|. If the property doesn't exist, returns
|
||||||
|
// |default_value|.
|
||||||
|
virtual std::string GetProperty(const std::string_view key,
|
||||||
|
const std::string_view default_value) const = 0;
|
||||||
|
|
||||||
|
// Given the name of the block device, returns |name| for updates on the device; or the file path
|
||||||
|
// to the fake block device for simulations.
|
||||||
|
virtual std::string FindBlockDeviceName(const std::string_view name) const = 0;
|
||||||
|
|
||||||
|
// Mounts the |location| on |mount_point|. Returns 0 on success.
|
||||||
|
virtual int Mount(const std::string_view location, const std::string_view mount_point,
|
||||||
|
const std::string_view fs_type, const std::string_view mount_options) = 0;
|
||||||
|
|
||||||
|
// Returns true if |mount_point| is mounted.
|
||||||
|
virtual bool IsMounted(const std::string_view mount_point) const = 0;
|
||||||
|
|
||||||
|
// Unmounts the |mount_point|. Returns a pair of results with the first value indicating
|
||||||
|
// if the |mount_point| is mounted, and the second value indicating the result of umount(2).
|
||||||
|
virtual std::pair<bool, int> Unmount(const std::string_view mount_point) = 0;
|
||||||
|
|
||||||
|
// Reads |filename| and puts its value to |content|.
|
||||||
|
virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0;
|
||||||
|
|
||||||
|
// Updates the content of |filename| with |content|.
|
||||||
|
virtual bool WriteStringToFile(const std::string_view content,
|
||||||
|
const std::string_view filename) const = 0;
|
||||||
|
|
||||||
|
// Wipes the first |len| bytes of block device in |filename|.
|
||||||
|
virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0;
|
||||||
|
|
||||||
|
// Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true.
|
||||||
|
virtual int RunProgram(const std::vector<std::string>& args, bool is_vfork) const = 0;
|
||||||
|
|
||||||
|
// Runs tune2fs with arguments |args|.
|
||||||
|
virtual int Tune2Fs(const std::vector<std::string>& args) const = 0;
|
||||||
|
|
||||||
|
// Dynamic partition related functions.
|
||||||
|
virtual bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) = 0;
|
||||||
|
virtual bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) = 0;
|
||||||
|
virtual bool UpdateDynamicPartitions(const std::string_view op_list_value) = 0;
|
||||||
|
|
||||||
|
// On devices supports A/B, add current slot suffix to arg. Otherwise, return |arg| as is.
|
||||||
|
virtual std::string AddSlotSuffix(const std::string_view arg) const = 0;
|
||||||
|
};
|
112
edify/lexer.ll
Normal file
112
edify/lexer.ll
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
%{
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 <string.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "yydefs.h"
|
||||||
|
#include "parser.h"
|
||||||
|
|
||||||
|
int gLine = 1;
|
||||||
|
int gColumn = 1;
|
||||||
|
int gPos = 0;
|
||||||
|
|
||||||
|
std::string string_buffer;
|
||||||
|
|
||||||
|
#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \
|
||||||
|
gColumn+=yyleng; gPos+=yyleng;} while(0)
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%x STR
|
||||||
|
|
||||||
|
%option noinput
|
||||||
|
%option nounput
|
||||||
|
%option noyywrap
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
|
||||||
|
\" {
|
||||||
|
BEGIN(STR);
|
||||||
|
string_buffer.clear();
|
||||||
|
yylloc.start = gPos;
|
||||||
|
++gColumn;
|
||||||
|
++gPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
<STR>{
|
||||||
|
\" {
|
||||||
|
++gColumn;
|
||||||
|
++gPos;
|
||||||
|
BEGIN(INITIAL);
|
||||||
|
yylval.str = strdup(string_buffer.c_str());
|
||||||
|
yylloc.end = gPos;
|
||||||
|
return STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
\\n { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\n'); }
|
||||||
|
\\t { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\t'); }
|
||||||
|
\\\" { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\"'); }
|
||||||
|
\\\\ { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\\'); }
|
||||||
|
|
||||||
|
\\x[0-9a-fA-F]{2} {
|
||||||
|
gColumn += yyleng;
|
||||||
|
gPos += yyleng;
|
||||||
|
int val;
|
||||||
|
sscanf(yytext+2, "%x", &val);
|
||||||
|
string_buffer.push_back(static_cast<char>(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
\n {
|
||||||
|
++gLine;
|
||||||
|
++gPos;
|
||||||
|
gColumn = 1;
|
||||||
|
string_buffer.push_back(yytext[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
. {
|
||||||
|
++gColumn;
|
||||||
|
++gPos;
|
||||||
|
string_buffer.push_back(yytext[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ADVANCE; return IF;
|
||||||
|
then ADVANCE; return THEN;
|
||||||
|
else ADVANCE; return ELSE;
|
||||||
|
endif ADVANCE; return ENDIF;
|
||||||
|
|
||||||
|
[a-zA-Z0-9_:/.]+ {
|
||||||
|
ADVANCE;
|
||||||
|
yylval.str = strdup(yytext);
|
||||||
|
return STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
\&\& ADVANCE; return AND;
|
||||||
|
\|\| ADVANCE; return OR;
|
||||||
|
== ADVANCE; return EQ;
|
||||||
|
!= ADVANCE; return NE;
|
||||||
|
|
||||||
|
[+(),!;] ADVANCE; return yytext[0];
|
||||||
|
|
||||||
|
[ \t]+ ADVANCE;
|
||||||
|
|
||||||
|
(#.*)?\n gPos += yyleng; ++gLine; gColumn = 1;
|
||||||
|
|
||||||
|
. return BAD;
|
145
edify/parser.yy
Normal file
145
edify/parser.yy
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
%{
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/macros.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "yydefs.h"
|
||||||
|
#include "parser.h"
|
||||||
|
|
||||||
|
extern int gLine;
|
||||||
|
extern int gColumn;
|
||||||
|
|
||||||
|
void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s);
|
||||||
|
int yyparse(std::unique_ptr<Expr>* root, int* error_count);
|
||||||
|
|
||||||
|
struct yy_buffer_state;
|
||||||
|
void yy_switch_to_buffer(struct yy_buffer_state* new_buffer);
|
||||||
|
struct yy_buffer_state* yy_scan_string(const char* yystr);
|
||||||
|
|
||||||
|
// Convenience function for building expressions with a fixed number
|
||||||
|
// of arguments.
|
||||||
|
static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) {
|
||||||
|
va_list v;
|
||||||
|
va_start(v, count);
|
||||||
|
Expr* e = new Expr(fn, "(operator)", loc.start, loc.end);
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
e->argv.emplace_back(va_arg(v, Expr*));
|
||||||
|
}
|
||||||
|
va_end(v);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%locations
|
||||||
|
|
||||||
|
%union {
|
||||||
|
char* str;
|
||||||
|
Expr* expr;
|
||||||
|
std::vector<std::unique_ptr<Expr>>* args;
|
||||||
|
}
|
||||||
|
|
||||||
|
%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
|
||||||
|
%token <str> STRING BAD
|
||||||
|
%type <expr> expr
|
||||||
|
%type <args> arglist
|
||||||
|
|
||||||
|
%destructor { delete $$; } expr
|
||||||
|
%destructor { delete $$; } arglist
|
||||||
|
|
||||||
|
%parse-param {std::unique_ptr<Expr>* root}
|
||||||
|
%parse-param {int* error_count}
|
||||||
|
%define parse.error verbose
|
||||||
|
|
||||||
|
/* declarations in increasing order of precedence */
|
||||||
|
%left ';'
|
||||||
|
%left ','
|
||||||
|
%left OR
|
||||||
|
%left AND
|
||||||
|
%left EQ NE
|
||||||
|
%left '+'
|
||||||
|
%right '!'
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
input: expr { root->reset($1); }
|
||||||
|
;
|
||||||
|
|
||||||
|
expr: STRING {
|
||||||
|
$$ = new Expr(Literal, $1, @$.start, @$.end);
|
||||||
|
}
|
||||||
|
| '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; }
|
||||||
|
| expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; }
|
||||||
|
| expr ';' expr { $$ = Build(SequenceFn, @$, 2, $1, $3); }
|
||||||
|
| error ';' expr { $$ = $3; $$->start=@$.start; $$->end=@$.end; }
|
||||||
|
| expr '+' expr { $$ = Build(ConcatFn, @$, 2, $1, $3); }
|
||||||
|
| expr EQ expr { $$ = Build(EqualityFn, @$, 2, $1, $3); }
|
||||||
|
| expr NE expr { $$ = Build(InequalityFn, @$, 2, $1, $3); }
|
||||||
|
| expr AND expr { $$ = Build(LogicalAndFn, @$, 2, $1, $3); }
|
||||||
|
| expr OR expr { $$ = Build(LogicalOrFn, @$, 2, $1, $3); }
|
||||||
|
| '!' expr { $$ = Build(LogicalNotFn, @$, 1, $2); }
|
||||||
|
| IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); }
|
||||||
|
| IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); }
|
||||||
|
| STRING '(' arglist ')' {
|
||||||
|
Function fn = FindFunction($1);
|
||||||
|
if (fn == nullptr) {
|
||||||
|
std::string msg = "unknown function \"" + std::string($1) + "\"";
|
||||||
|
yyerror(root, error_count, msg.c_str());
|
||||||
|
YYERROR;
|
||||||
|
}
|
||||||
|
$$ = new Expr(fn, $1, @$.start, @$.end);
|
||||||
|
$$->argv = std::move(*$3);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
arglist: /* empty */ {
|
||||||
|
$$ = new std::vector<std::unique_ptr<Expr>>;
|
||||||
|
}
|
||||||
|
| expr {
|
||||||
|
$$ = new std::vector<std::unique_ptr<Expr>>;
|
||||||
|
$$->emplace_back($1);
|
||||||
|
}
|
||||||
|
| arglist ',' expr {
|
||||||
|
UNUSED($1);
|
||||||
|
$$->push_back(std::unique_ptr<Expr>($3));
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s) {
|
||||||
|
if (strlen(s) == 0) {
|
||||||
|
s = "syntax error";
|
||||||
|
}
|
||||||
|
printf("line %d col %d: %s\n", gLine, gColumn, s);
|
||||||
|
++*error_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ParseString(const std::string& str, std::unique_ptr<Expr>* root, int* error_count) {
|
||||||
|
yy_switch_to_buffer(yy_scan_string(str.c_str()));
|
||||||
|
return yyparse(root, error_count);
|
||||||
|
}
|
38
edify/yydefs.h
Normal file
38
edify/yydefs.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 _YYDEFS_H_
|
||||||
|
#define _YYDEFS_H_
|
||||||
|
|
||||||
|
#define YYLTYPE YYLTYPE
|
||||||
|
typedef struct {
|
||||||
|
int start, end;
|
||||||
|
} YYLTYPE;
|
||||||
|
|
||||||
|
#define YYLLOC_DEFAULT(Current, Rhs, N) \
|
||||||
|
do { \
|
||||||
|
if (N) { \
|
||||||
|
(Current).start = YYRHSLOC(Rhs, 1).start; \
|
||||||
|
(Current).end = YYRHSLOC(Rhs, N).end; \
|
||||||
|
} else { \
|
||||||
|
(Current).start = YYRHSLOC(Rhs, 0).start; \
|
||||||
|
(Current).end = YYRHSLOC(Rhs, 0).end; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
int yylex();
|
||||||
|
|
||||||
|
#endif
|
133
tests/Android.bp
Normal file
133
tests/Android.bp
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright (C) 2024 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.
|
||||||
|
|
||||||
|
|
||||||
|
cc_test_host {
|
||||||
|
name: "recovery_host_test",
|
||||||
|
isolated: true,
|
||||||
|
|
||||||
|
include_dirs: [
|
||||||
|
"bootable/deprecated-ota",
|
||||||
|
"bootable/recovery/tests",
|
||||||
|
],
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"recovery_test_defaults",
|
||||||
|
"libupdater_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
tidy_timeout_srcs: [
|
||||||
|
"unit/host/imgdiff_test.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"unit/host/*",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libupdater_host",
|
||||||
|
"libupdater_core",
|
||||||
|
"libimgdiff",
|
||||||
|
"libbsdiff",
|
||||||
|
"libdivsufsort64",
|
||||||
|
"libdivsufsort",
|
||||||
|
"libfstab",
|
||||||
|
"libc++fs",
|
||||||
|
],
|
||||||
|
|
||||||
|
test_suites: ["general-tests"],
|
||||||
|
test_config: "RecoveryHostTest.xml",
|
||||||
|
|
||||||
|
data: ["testdata/*"],
|
||||||
|
|
||||||
|
target: {
|
||||||
|
darwin: {
|
||||||
|
// libapplypatch in "libupdater_defaults" is not available on the Mac.
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// libapplypatch, libapplypatch_modes
|
||||||
|
libapplypatch_static_libs = [
|
||||||
|
"libapplypatch_modes",
|
||||||
|
"libapplypatch",
|
||||||
|
"libedify",
|
||||||
|
"libotautil",
|
||||||
|
"libbsdiff",
|
||||||
|
"libbspatch",
|
||||||
|
"libdivsufsort",
|
||||||
|
"libdivsufsort64",
|
||||||
|
"libutils",
|
||||||
|
"libbase",
|
||||||
|
"libbrotli",
|
||||||
|
"libbz",
|
||||||
|
"libz_stable",
|
||||||
|
"libziparchive",
|
||||||
|
]
|
||||||
|
|
||||||
|
cc_test {
|
||||||
|
name: "non_ab_unit_tests",
|
||||||
|
isolated: true,
|
||||||
|
require_root: true,
|
||||||
|
include_dirs: [
|
||||||
|
"bootable/deprecated-ota",
|
||||||
|
"bootable/recovery/tests",
|
||||||
|
],
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"recovery_test_defaults",
|
||||||
|
"libupdater_defaults",
|
||||||
|
"libupdater_device_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
test_suites: ["device-tests"],
|
||||||
|
|
||||||
|
tidy_timeout_srcs: [
|
||||||
|
"unit/commands_test.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"unit/*.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"libbinder_ndk",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: libapplypatch_static_libs + [
|
||||||
|
"android.hardware.health-translate-ndk",
|
||||||
|
"android.hardware.health-V3-ndk",
|
||||||
|
"libhealthshim",
|
||||||
|
"librecovery_ui",
|
||||||
|
"libfusesideload",
|
||||||
|
"libminui",
|
||||||
|
"librecovery_utils",
|
||||||
|
"libotautil",
|
||||||
|
"libupdater_device",
|
||||||
|
"libupdater_core",
|
||||||
|
"libupdate_verifier",
|
||||||
|
|
||||||
|
"libprotobuf-cpp-lite",
|
||||||
|
],
|
||||||
|
header_libs: [
|
||||||
|
"libgtest_prod_headers",
|
||||||
|
],
|
||||||
|
|
||||||
|
data: [
|
||||||
|
"testdata/*",
|
||||||
|
":recovery_image",
|
||||||
|
":res-testdata",
|
||||||
|
],
|
||||||
|
}
|
28
tests/RecoveryHostTest.xml
Normal file
28
tests/RecoveryHostTest.xml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<configuration description="Runs recovery_host_test.">
|
||||||
|
<option name="null-device" value="true" />
|
||||||
|
|
||||||
|
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
|
||||||
|
<option name="force-root" value="false" />
|
||||||
|
</target_preparer>
|
||||||
|
<option name="not-shardable" value="true" />
|
||||||
|
|
||||||
|
<test class="com.android.tradefed.testtype.HostGTest" >
|
||||||
|
<option name="module-name" value="recovery_host_test" />
|
||||||
|
<option name="native-test-timeout" value="5m"/>
|
||||||
|
</test>
|
||||||
|
</configuration>
|
BIN
tests/testdata/deflate_src.zip
vendored
Normal file
BIN
tests/testdata/deflate_src.zip
vendored
Normal file
Binary file not shown.
BIN
tests/testdata/deflate_tgt.zip
vendored
Normal file
BIN
tests/testdata/deflate_tgt.zip
vendored
Normal file
Binary file not shown.
BIN
tests/testdata/gzipped_source
vendored
Normal file
BIN
tests/testdata/gzipped_source
vendored
Normal file
Binary file not shown.
BIN
tests/testdata/gzipped_target
vendored
Normal file
BIN
tests/testdata/gzipped_target
vendored
Normal file
Binary file not shown.
198
tests/unit/applypatch_modes_test.cpp
Normal file
198
tests/unit/applypatch_modes_test.cpp
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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 agree 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <bsdiff/bsdiff.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "applypatch/applypatch_modes.h"
|
||||||
|
#include "common/test_constants.h"
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
#include "otautil/sysutil.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
// Loads a given partition and returns a string of form "EMMC:name:size:hash".
|
||||||
|
static std::string GetEmmcTargetString(const std::string& filename,
|
||||||
|
const std::string& display_name = "") {
|
||||||
|
std::string data;
|
||||||
|
if (!android::base::ReadFileToString(filename, &data)) {
|
||||||
|
PLOG(ERROR) << "Failed to read " << filename;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
|
||||||
|
|
||||||
|
return "EMMC:"s + (display_name.empty() ? filename : display_name) + ":" +
|
||||||
|
std::to_string(data.size()) + ":" + print_sha1(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApplyPatchModesTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
source = GetEmmcTargetString(from_testdata_base("boot.img"));
|
||||||
|
ASSERT_FALSE(source.empty());
|
||||||
|
|
||||||
|
std::string recovery_file = from_testdata_base("recovery.img");
|
||||||
|
recovery = GetEmmcTargetString(recovery_file);
|
||||||
|
ASSERT_FALSE(recovery.empty());
|
||||||
|
|
||||||
|
ASSERT_TRUE(android::base::WriteStringToFile("", patched_file_.path));
|
||||||
|
target = GetEmmcTargetString(recovery_file, patched_file_.path);
|
||||||
|
ASSERT_FALSE(target.empty());
|
||||||
|
|
||||||
|
Paths::Get().set_cache_temp_source(cache_source_.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source;
|
||||||
|
std::string target;
|
||||||
|
std::string recovery;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TemporaryFile cache_source_;
|
||||||
|
TemporaryFile patched_file_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int InvokeApplyPatchModes(const std::vector<std::string>& args) {
|
||||||
|
auto args_to_call = StringVectorToNullTerminatedArray(args);
|
||||||
|
return applypatch_modes(args_to_call.size() - 1, args_to_call.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void VerifyPatchedTarget(const std::string& target) {
|
||||||
|
std::vector<std::string> pieces = android::base::Split(target, ":");
|
||||||
|
ASSERT_EQ(4, pieces.size());
|
||||||
|
ASSERT_EQ("EMMC", pieces[0]);
|
||||||
|
|
||||||
|
std::string patched_emmc = GetEmmcTargetString(pieces[1]);
|
||||||
|
ASSERT_FALSE(patched_emmc.empty());
|
||||||
|
ASSERT_EQ(target, patched_emmc);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, InvalidArgs) {
|
||||||
|
// At least two args (including the filename).
|
||||||
|
ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch" }));
|
||||||
|
|
||||||
|
// Unrecognized args.
|
||||||
|
ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "-x" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) {
|
||||||
|
std::vector<std::string> args{
|
||||||
|
"applypatch",
|
||||||
|
"--bonus",
|
||||||
|
from_testdata_base("bonus.file"),
|
||||||
|
"--patch",
|
||||||
|
from_testdata_base("recovery-from-boot.p"),
|
||||||
|
"--target",
|
||||||
|
target,
|
||||||
|
"--source",
|
||||||
|
source,
|
||||||
|
};
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||||
|
VerifyPatchedTarget(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
|
||||||
|
// everything).
|
||||||
|
TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) {
|
||||||
|
std::vector<std::string> args{
|
||||||
|
"applypatch", "--patch", from_testdata_base("recovery-from-boot-with-bonus.p"),
|
||||||
|
"--target", target, "--source",
|
||||||
|
source,
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||||
|
VerifyPatchedTarget(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
|
||||||
|
TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
|
||||||
|
// Generate the bsdiff patch of recovery-from-boot.p.
|
||||||
|
std::string src_content;
|
||||||
|
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("boot.img"), &src_content));
|
||||||
|
|
||||||
|
std::string tgt_content;
|
||||||
|
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("recovery.img"), &tgt_content));
|
||||||
|
|
||||||
|
TemporaryFile patch_file;
|
||||||
|
ASSERT_EQ(0,
|
||||||
|
bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
|
||||||
|
reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
|
||||||
|
patch_file.path, nullptr));
|
||||||
|
|
||||||
|
std::vector<std::string> args{
|
||||||
|
"applypatch", "--patch", patch_file.path, "--target", target, "--source", source,
|
||||||
|
};
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||||
|
VerifyPatchedTarget(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) {
|
||||||
|
// Invalid bonus file.
|
||||||
|
std::vector<std::string> args{
|
||||||
|
"applypatch", "--bonus", "/doesntexist", "--patch", from_testdata_base("recovery-from-boot.p"),
|
||||||
|
"--target", target, "--source", source,
|
||||||
|
};
|
||||||
|
ASSERT_NE(0, InvokeApplyPatchModes(args));
|
||||||
|
|
||||||
|
// With bonus file, but missing args.
|
||||||
|
ASSERT_NE(0,
|
||||||
|
InvokeApplyPatchModes({ "applypatch", "--bonus", from_testdata_base("bonus.file") }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, FlashMode) {
|
||||||
|
std::vector<std::string> args{
|
||||||
|
"applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target,
|
||||||
|
};
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||||
|
VerifyPatchedTarget(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) {
|
||||||
|
std::vector<std::string> args{
|
||||||
|
"applypatch", "--bonus", from_testdata_base("bonus.file"), "--flash", source,
|
||||||
|
"--target", target,
|
||||||
|
};
|
||||||
|
ASSERT_NE(0, InvokeApplyPatchModes(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, CheckMode) {
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", recovery }));
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", source }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) {
|
||||||
|
ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "--check" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, CheckModeNonEmmcTarget) {
|
||||||
|
ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--check", from_testdata_base("boot.img") }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchModesTest, ShowLicenses) {
|
||||||
|
ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--license" }));
|
||||||
|
}
|
290
tests/unit/applypatch_test.cpp
Normal file
290
tests/unit/applypatch_test.cpp
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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 agree 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 <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "applypatch/applypatch.h"
|
||||||
|
#include "common/test_constants.h"
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
class ApplyPatchTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
source_file = from_testdata_base("boot.img");
|
||||||
|
FileContents boot_fc;
|
||||||
|
ASSERT_TRUE(LoadFileContents(source_file, &boot_fc));
|
||||||
|
source_size = boot_fc.data.size();
|
||||||
|
source_sha1 = print_sha1(boot_fc.sha1);
|
||||||
|
|
||||||
|
target_file = from_testdata_base("recovery.img");
|
||||||
|
FileContents recovery_fc;
|
||||||
|
ASSERT_TRUE(LoadFileContents(target_file, &recovery_fc));
|
||||||
|
target_size = recovery_fc.data.size();
|
||||||
|
target_sha1 = print_sha1(recovery_fc.sha1);
|
||||||
|
|
||||||
|
source_partition = Partition(source_file, source_size, source_sha1);
|
||||||
|
target_partition = Partition(partition_file.path, target_size, target_sha1);
|
||||||
|
|
||||||
|
srand(time(nullptr));
|
||||||
|
bad_sha1_a = android::base::StringPrintf("%040x", rand());
|
||||||
|
bad_sha1_b = android::base::StringPrintf("%040x", rand());
|
||||||
|
|
||||||
|
// Reset the cache backup file.
|
||||||
|
Paths::Get().set_cache_temp_source(cache_temp_source.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source_file;
|
||||||
|
std::string source_sha1;
|
||||||
|
size_t source_size;
|
||||||
|
|
||||||
|
std::string target_file;
|
||||||
|
std::string target_sha1;
|
||||||
|
size_t target_size;
|
||||||
|
|
||||||
|
std::string bad_sha1_a;
|
||||||
|
std::string bad_sha1_b;
|
||||||
|
|
||||||
|
Partition source_partition;
|
||||||
|
Partition target_partition;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TemporaryFile partition_file;
|
||||||
|
TemporaryFile cache_temp_source;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchTest, CheckPartition) {
|
||||||
|
ASSERT_TRUE(CheckPartition(source_partition));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchTest, CheckPartition_Mismatching) {
|
||||||
|
ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1)));
|
||||||
|
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a)));
|
||||||
|
|
||||||
|
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1)));
|
||||||
|
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchTest, PatchPartitionCheck) {
|
||||||
|
ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition));
|
||||||
|
|
||||||
|
ASSERT_TRUE(
|
||||||
|
PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition));
|
||||||
|
|
||||||
|
ASSERT_TRUE(
|
||||||
|
PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) {
|
||||||
|
ASSERT_FALSE(
|
||||||
|
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||||
|
|
||||||
|
Paths::Get().set_cache_temp_source(source_file);
|
||||||
|
ASSERT_TRUE(
|
||||||
|
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) {
|
||||||
|
ASSERT_FALSE(
|
||||||
|
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||||
|
|
||||||
|
Paths::Get().set_cache_temp_source(target_file);
|
||||||
|
ASSERT_FALSE(
|
||||||
|
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ApplyPatchTest, PatchPartition) {
|
||||||
|
FileContents patch_fc;
|
||||||
|
ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc));
|
||||||
|
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
|
||||||
|
|
||||||
|
FileContents bonus_fc;
|
||||||
|
ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
|
||||||
|
Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
|
||||||
|
|
||||||
|
ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
|
||||||
|
// everything).
|
||||||
|
TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) {
|
||||||
|
FileContents patch_fc;
|
||||||
|
ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
|
||||||
|
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
|
||||||
|
|
||||||
|
ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
class FreeCacheTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
static constexpr size_t PARTITION_SIZE = 4096 * 10;
|
||||||
|
|
||||||
|
// Returns a sorted list of files in |dirname|.
|
||||||
|
static std::vector<std::string> FindFilesInDir(const std::string& dirname) {
|
||||||
|
std::vector<std::string> file_list;
|
||||||
|
|
||||||
|
std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
|
||||||
|
struct dirent* de;
|
||||||
|
while ((de = readdir(d.get())) != 0) {
|
||||||
|
std::string path = dirname + "/" + de->d_name;
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
|
||||||
|
file_list.emplace_back(de->d_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(file_list.begin(), file_list.end());
|
||||||
|
return file_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFilesToDir(const std::string& dir, const std::vector<std::string>& files) {
|
||||||
|
std::string zeros(4096, 0);
|
||||||
|
for (const auto& file : files) {
|
||||||
|
temporary_files_.push_back(dir + "/" + file);
|
||||||
|
ASSERT_TRUE(android::base::WriteStringToFile(zeros, temporary_files_.back()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
Paths::Get().set_cache_log_directory(mock_log_dir.path);
|
||||||
|
temporary_files_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
for (const auto& file : temporary_files_) {
|
||||||
|
ASSERT_TRUE(android::base::RemoveFileIfExists(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A mock method to calculate the free space. It assumes the partition has a total size of 40960
|
||||||
|
// bytes and all files are 4096 bytes in size.
|
||||||
|
static size_t MockFreeSpaceChecker(const std::string& dirname) {
|
||||||
|
std::vector<std::string> files = FindFilesInDir(dirname);
|
||||||
|
return PARTITION_SIZE - 4096 * files.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
TemporaryDir mock_cache;
|
||||||
|
TemporaryDir mock_log_dir;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> temporary_files_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(FreeCacheTest, FreeCacheSmoke) {
|
||||||
|
std::vector<std::string> files = { "file1", "file2", "file3" };
|
||||||
|
AddFilesToDir(mock_cache.path, files);
|
||||||
|
ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
|
||||||
|
ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
|
||||||
|
|
||||||
|
ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_cache.path, MockFreeSpaceChecker));
|
||||||
|
|
||||||
|
ASSERT_EQ(std::vector<std::string>{ "file3" }, FindFilesInDir(mock_cache.path));
|
||||||
|
ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) {
|
||||||
|
std::vector<std::string> files{ "file1", "file2", "file3" };
|
||||||
|
AddFilesToDir(mock_cache.path, files);
|
||||||
|
ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
|
||||||
|
ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
|
||||||
|
|
||||||
|
ASSERT_FALSE(
|
||||||
|
RemoveFilesInDirectory(4096 * 9, mock_cache.path, [](const std::string&) { return -1; }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FreeCacheTest, FreeCacheOpenFile) {
|
||||||
|
std::vector<std::string> files = { "file1", "file2" };
|
||||||
|
AddFilesToDir(mock_cache.path, files);
|
||||||
|
ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
|
||||||
|
ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_cache.path));
|
||||||
|
|
||||||
|
std::string file1_path = mock_cache.path + "/file1"s;
|
||||||
|
android::base::unique_fd fd(open(file1_path.c_str(), O_RDONLY));
|
||||||
|
|
||||||
|
// file1 can't be deleted as it's opened by us.
|
||||||
|
ASSERT_FALSE(RemoveFilesInDirectory(4096 * 10, mock_cache.path, MockFreeSpaceChecker));
|
||||||
|
|
||||||
|
ASSERT_EQ(std::vector<std::string>{ "file1" }, FindFilesInDir(mock_cache.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FreeCacheTest, FreeCacheLogsSmoke) {
|
||||||
|
std::vector<std::string> log_files = { "last_log", "last_log.1", "last_kmsg.2", "last_log.5",
|
||||||
|
"last_log.10" };
|
||||||
|
AddFilesToDir(mock_log_dir.path, log_files);
|
||||||
|
ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
|
||||||
|
|
||||||
|
ASSERT_TRUE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
|
||||||
|
|
||||||
|
// Logs with a higher index will be deleted first
|
||||||
|
std::vector<std::string> expected = { "last_log", "last_log.1" };
|
||||||
|
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
|
||||||
|
ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_log_dir.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FreeCacheTest, FreeCacheLogsStringComparison) {
|
||||||
|
std::vector<std::string> log_files = { "last_log.1", "last_kmsg.1", "last_log.not_number",
|
||||||
|
"last_kmsgrandom" };
|
||||||
|
AddFilesToDir(mock_log_dir.path, log_files);
|
||||||
|
ASSERT_EQ(4096 * 6, MockFreeSpaceChecker(mock_log_dir.path));
|
||||||
|
|
||||||
|
ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_log_dir.path, MockFreeSpaceChecker));
|
||||||
|
|
||||||
|
// Logs with incorrect format will be deleted first; and the last_kmsg with the same index is
|
||||||
|
// deleted before last_log.
|
||||||
|
std::vector<std::string> expected = { "last_log.1" };
|
||||||
|
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
|
||||||
|
ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_log_dir.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FreeCacheTest, FreeCacheLogsOtherFiles) {
|
||||||
|
std::vector<std::string> log_files = { "last_install", "command", "block.map", "last_log",
|
||||||
|
"last_kmsg.1" };
|
||||||
|
AddFilesToDir(mock_log_dir.path, log_files);
|
||||||
|
ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
|
||||||
|
|
||||||
|
ASSERT_FALSE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
|
||||||
|
|
||||||
|
// Non log files in /cache/recovery won't be deleted.
|
||||||
|
std::vector<std::string> expected = { "block.map", "command", "last_install" };
|
||||||
|
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
|
||||||
|
}
|
554
tests/unit/commands_test.cpp
Normal file
554
tests/unit/commands_test.cpp
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
#include "otautil/rangeset.h"
|
||||||
|
#include "private/commands.h"
|
||||||
|
|
||||||
|
TEST(CommandsTest, ParseType) {
|
||||||
|
ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero"));
|
||||||
|
ASSERT_EQ(Command::Type::NEW, Command::ParseType("new"));
|
||||||
|
ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase"));
|
||||||
|
ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move"));
|
||||||
|
ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff"));
|
||||||
|
ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff"));
|
||||||
|
ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash"));
|
||||||
|
ASSERT_EQ(Command::Type::FREE, Command::ParseType("free"));
|
||||||
|
ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, Command::ParseType("compute_hash_tree"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, ParseType_InvalidCommand) {
|
||||||
|
ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo"));
|
||||||
|
ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) {
|
||||||
|
const std::vector<std::string> tokens{
|
||||||
|
"4,569884,569904,591946,592043",
|
||||||
|
"117",
|
||||||
|
"4,566779,566799,591946,592043",
|
||||||
|
};
|
||||||
|
TargetInfo target;
|
||||||
|
SourceInfo source;
|
||||||
|
std::string err;
|
||||||
|
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
|
||||||
|
tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||||
|
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||||
|
ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
|
||||||
|
target);
|
||||||
|
ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}),
|
||||||
|
source);
|
||||||
|
ASSERT_EQ(117, source.blocks());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) {
|
||||||
|
const std::vector<std::string> tokens{
|
||||||
|
"2,350729,350731",
|
||||||
|
"2",
|
||||||
|
"-",
|
||||||
|
"6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2",
|
||||||
|
};
|
||||||
|
TargetInfo target;
|
||||||
|
SourceInfo source;
|
||||||
|
std::string err;
|
||||||
|
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
|
||||||
|
tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target,
|
||||||
|
"1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err));
|
||||||
|
ASSERT_EQ(
|
||||||
|
TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })),
|
||||||
|
target);
|
||||||
|
ASSERT_EQ(
|
||||||
|
SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {},
|
||||||
|
{
|
||||||
|
StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })),
|
||||||
|
}),
|
||||||
|
source);
|
||||||
|
ASSERT_EQ(2, source.blocks());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) {
|
||||||
|
const std::vector<std::string> tokens{
|
||||||
|
"4,611641,611643,636981,637075",
|
||||||
|
"96",
|
||||||
|
"4,636981,637075,770665,770666",
|
||||||
|
"4,0,94,95,96",
|
||||||
|
"9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
|
||||||
|
};
|
||||||
|
TargetInfo target;
|
||||||
|
SourceInfo source;
|
||||||
|
std::string err;
|
||||||
|
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
|
||||||
|
tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target,
|
||||||
|
"a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err));
|
||||||
|
ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6",
|
||||||
|
RangeSet({ { 611641, 611643 }, { 636981, 637075 } })),
|
||||||
|
target);
|
||||||
|
ASSERT_EQ(SourceInfo(
|
||||||
|
"a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
|
||||||
|
RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges
|
||||||
|
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
|
||||||
|
{
|
||||||
|
StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })),
|
||||||
|
}),
|
||||||
|
source);
|
||||||
|
ASSERT_EQ(96, source.blocks());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) {
|
||||||
|
const std::vector<std::string> tokens{
|
||||||
|
"4,611641,611643,636981,637075",
|
||||||
|
"96",
|
||||||
|
"4,636981,637075,770665,770666",
|
||||||
|
"4,0,94,95,96",
|
||||||
|
"9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
|
||||||
|
};
|
||||||
|
TargetInfo target;
|
||||||
|
SourceInfo source;
|
||||||
|
std::string err;
|
||||||
|
|
||||||
|
// Mismatching block count.
|
||||||
|
{
|
||||||
|
std::vector<std::string> tokens_copy(tokens);
|
||||||
|
tokens_copy[1] = "97";
|
||||||
|
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
|
||||||
|
tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||||
|
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excess stashes (causing block count mismatch).
|
||||||
|
{
|
||||||
|
std::vector<std::string> tokens_copy(tokens);
|
||||||
|
tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22");
|
||||||
|
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
|
||||||
|
tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||||
|
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid args.
|
||||||
|
for (size_t i = 0; i < tokens.size(); i++) {
|
||||||
|
TargetInfo target;
|
||||||
|
SourceInfo source;
|
||||||
|
std::string err;
|
||||||
|
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
|
||||||
|
std::vector<std::string>(tokens.cbegin() + i + 1, tokens.cend()),
|
||||||
|
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||||
|
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_EmptyInput) {
|
||||||
|
std::string err;
|
||||||
|
ASSERT_FALSE(Command::Parse("", 0, &err));
|
||||||
|
ASSERT_EQ("invalid type", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_ABORT_Allowed) {
|
||||||
|
Command::abort_allowed_ = true;
|
||||||
|
|
||||||
|
const std::string input{ "abort" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 0, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo(), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_ABORT_NotAllowed) {
|
||||||
|
const std::string input{ "abort" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 0, &err);
|
||||||
|
ASSERT_FALSE(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_BSDIFF) {
|
||||||
|
const std::string input{
|
||||||
|
"bsdiff 0 148 "
|
||||||
|
"f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 "
|
||||||
|
"4,565704,565752,566779,566799 "
|
||||||
|
"68 4,64525,64545,565704,565752"
|
||||||
|
};
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 1, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::BSDIFF, command.type());
|
||||||
|
ASSERT_EQ(1, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599",
|
||||||
|
RangeSet({ { 565704, 565752 }, { 566779, 566799 } })),
|
||||||
|
command.target());
|
||||||
|
ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c",
|
||||||
|
RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}),
|
||||||
|
command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(0, 148), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_ERASE) {
|
||||||
|
const std::string input{ "erase 2,5,10" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 2, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::ERASE, command.type());
|
||||||
|
ASSERT_EQ(2, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_FREE) {
|
||||||
|
const std::string input{ "free hash1" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 3, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::FREE, command.type());
|
||||||
|
ASSERT_EQ(3, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo(), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_IMGDIFF) {
|
||||||
|
const std::string input{
|
||||||
|
"imgdiff 29629269 185 "
|
||||||
|
"a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 "
|
||||||
|
"2,90851,90852 "
|
||||||
|
"1 2,90851,90852"
|
||||||
|
};
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 4, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::IMGDIFF, command.type());
|
||||||
|
ASSERT_EQ(4, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })),
|
||||||
|
command.target());
|
||||||
|
ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }),
|
||||||
|
RangeSet(), {}),
|
||||||
|
command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(29629269, 185), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_MOVE) {
|
||||||
|
const std::string input{
|
||||||
|
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb "
|
||||||
|
"4,569884,569904,591946,592043 117 4,566779,566799,591946,592043"
|
||||||
|
};
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 5, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::MOVE, command.type());
|
||||||
|
ASSERT_EQ(5, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
|
||||||
|
command.target());
|
||||||
|
ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}),
|
||||||
|
command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_NEW) {
|
||||||
|
const std::string input{ "new 4,3,5,10,12" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 6, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::NEW, command.type());
|
||||||
|
ASSERT_EQ(6, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_STASH) {
|
||||||
|
const std::string input{ "stash hash1 2,5,10" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 7, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::STASH, command.type());
|
||||||
|
ASSERT_EQ(7, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo(), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_ZERO) {
|
||||||
|
const std::string input{ "zero 2,1,5" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 8, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::ZERO, command.type());
|
||||||
|
ASSERT_EQ(8, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_COMPUTE_HASH_TREE) {
|
||||||
|
const std::string input{ "compute_hash_tree 2,0,1 2,3,4 sha1 unknown-salt unknown-root-hash" };
|
||||||
|
std::string err;
|
||||||
|
Command command = Command::Parse(input, 9, &err);
|
||||||
|
ASSERT_TRUE(command);
|
||||||
|
|
||||||
|
ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, command.type());
|
||||||
|
ASSERT_EQ(9, command.index());
|
||||||
|
ASSERT_EQ(input, command.cmdline());
|
||||||
|
|
||||||
|
HashTreeInfo expected_info(RangeSet({ { 0, 1 } }), RangeSet({ { 3, 4 } }), "sha1", "unknown-salt",
|
||||||
|
"unknown-root-hash");
|
||||||
|
ASSERT_EQ(expected_info, command.hash_tree_info());
|
||||||
|
ASSERT_EQ(TargetInfo(), command.target());
|
||||||
|
ASSERT_EQ(SourceInfo(), command.source());
|
||||||
|
ASSERT_EQ(StashInfo(), command.stash());
|
||||||
|
ASSERT_EQ(PatchInfo(), command.patch());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CommandsTest, Parse_InvalidNumberOfArgs) {
|
||||||
|
Command::abort_allowed_ = true;
|
||||||
|
|
||||||
|
// Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by
|
||||||
|
// ParseTargetInfoAndSourceInfo_InvalidInput.
|
||||||
|
std::vector<std::string> inputs{
|
||||||
|
"abort foo",
|
||||||
|
"bsdiff",
|
||||||
|
"compute_hash_tree, 2,0,1 2,0,1 unknown-algorithm unknown-salt",
|
||||||
|
"erase",
|
||||||
|
"erase 4,3,5,10,12 hash1",
|
||||||
|
"free",
|
||||||
|
"free id1 id2",
|
||||||
|
"imgdiff",
|
||||||
|
"move",
|
||||||
|
"new",
|
||||||
|
"new 4,3,5,10,12 hash1",
|
||||||
|
"stash",
|
||||||
|
"stash id1",
|
||||||
|
"stash id1 4,3,5,10,12 id2",
|
||||||
|
"zero",
|
||||||
|
"zero 4,3,5,10,12 hash2",
|
||||||
|
};
|
||||||
|
for (const auto& input : inputs) {
|
||||||
|
std::string err;
|
||||||
|
ASSERT_FALSE(Command::Parse(input, 0, &err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, Overlaps) {
|
||||||
|
ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||||
|
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 7, 9 }, { 16, 20 } }))));
|
||||||
|
|
||||||
|
ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||||
|
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 4, 7 }, { 16, 23 } }))));
|
||||||
|
|
||||||
|
ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||||
|
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 9, 16 } }))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, Overlaps_EmptySourceOrTarget) {
|
||||||
|
ASSERT_FALSE(SourceInfo().Overlaps(TargetInfo()));
|
||||||
|
|
||||||
|
ASSERT_FALSE(SourceInfo().Overlaps(
|
||||||
|
TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", RangeSet({ { 7, 9 }, { 16, 20 } }))));
|
||||||
|
|
||||||
|
ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||||
|
.Overlaps(TargetInfo()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, Overlaps_WithStashes) {
|
||||||
|
ASSERT_FALSE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
|
||||||
|
RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
|
||||||
|
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
|
||||||
|
{ StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
|
||||||
|
RangeSet({ { 94, 95 } })) })
|
||||||
|
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 175, 265 } }))));
|
||||||
|
|
||||||
|
ASSERT_TRUE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
|
||||||
|
RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
|
||||||
|
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
|
||||||
|
{ StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
|
||||||
|
RangeSet({ { 94, 95 } })) })
|
||||||
|
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||||
|
RangeSet({ { 265, 266 } }))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block size should be specified by the caller of ReadAll (i.e. from Command instance during
|
||||||
|
// normal run).
|
||||||
|
constexpr size_t kBlockSize = 4096;
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, ReadAll) {
|
||||||
|
// "2727756cfee3fbfe24bf5650123fd7743d7b3465" is the SHA-1 hex digest of 8192 * 'a'.
|
||||||
|
const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
|
||||||
|
{});
|
||||||
|
auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
|
||||||
|
std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
|
||||||
|
std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
|
||||||
|
ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
|
||||||
|
ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
|
||||||
|
|
||||||
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(buffer.data(), buffer.size(), digest);
|
||||||
|
ASSERT_EQ(source.hash(), print_sha1(digest));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, ReadAll_WithStashes) {
|
||||||
|
const SourceInfo source(
|
||||||
|
// SHA-1 hex digest of 8192 * 'a' + 4096 * 'b'.
|
||||||
|
"ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
|
||||||
|
{ StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
|
||||||
|
auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
|
||||||
|
std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
auto stash_reader = [](const std::string&, std::vector<uint8_t>* stash_buffer) -> int {
|
||||||
|
std::fill_n(stash_buffer->begin(), kBlockSize, 'b');
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
|
||||||
|
ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
|
||||||
|
ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
|
||||||
|
|
||||||
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(buffer.data(), buffer.size(), digest);
|
||||||
|
ASSERT_EQ(source.hash(), print_sha1(digest));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, ReadAll_BufferTooSmall) {
|
||||||
|
const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
|
||||||
|
{});
|
||||||
|
auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
|
||||||
|
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
|
||||||
|
std::vector<uint8_t> buffer(source.blocks() * kBlockSize - 1);
|
||||||
|
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SourceInfoTest, ReadAll_FailingReader) {
|
||||||
|
const SourceInfo source(
|
||||||
|
"ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
|
||||||
|
{ StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
|
||||||
|
std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
|
||||||
|
auto failing_block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return -1; };
|
||||||
|
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
|
||||||
|
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader));
|
||||||
|
|
||||||
|
auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
|
||||||
|
auto failing_stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return -1; };
|
||||||
|
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransferListTest, Parse) {
|
||||||
|
std::vector<std::string> input_lines{
|
||||||
|
"4", // version
|
||||||
|
"2", // total blocks
|
||||||
|
"1", // max stashed entries
|
||||||
|
"1", // max stashed blocks
|
||||||
|
"stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
|
||||||
|
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1 2,0,1",
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
|
||||||
|
ASSERT_TRUE(static_cast<bool>(transfer_list));
|
||||||
|
ASSERT_EQ(4, transfer_list.version());
|
||||||
|
ASSERT_EQ(2, transfer_list.total_blocks());
|
||||||
|
ASSERT_EQ(1, transfer_list.stash_max_entries());
|
||||||
|
ASSERT_EQ(1, transfer_list.stash_max_blocks());
|
||||||
|
ASSERT_EQ(2U, transfer_list.commands().size());
|
||||||
|
ASSERT_EQ(Command::Type::STASH, transfer_list.commands()[0].type());
|
||||||
|
ASSERT_EQ(Command::Type::MOVE, transfer_list.commands()[1].type());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransferListTest, Parse_InvalidCommand) {
|
||||||
|
std::vector<std::string> input_lines{
|
||||||
|
"4", // version
|
||||||
|
"2", // total blocks
|
||||||
|
"1", // max stashed entries
|
||||||
|
"1", // max stashed blocks
|
||||||
|
"stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
|
||||||
|
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1",
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
|
||||||
|
ASSERT_FALSE(static_cast<bool>(transfer_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TransferListTest, Parse_ZeroTotalBlocks) {
|
||||||
|
std::vector<std::string> input_lines{
|
||||||
|
"4", // version
|
||||||
|
"0", // total blocks
|
||||||
|
"0", // max stashed entries
|
||||||
|
"0", // max stashed blocks
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
|
||||||
|
ASSERT_TRUE(static_cast<bool>(transfer_list));
|
||||||
|
ASSERT_EQ(4, transfer_list.version());
|
||||||
|
ASSERT_EQ(0, transfer_list.total_blocks());
|
||||||
|
ASSERT_EQ(0, transfer_list.stash_max_entries());
|
||||||
|
ASSERT_EQ(0, transfer_list.stash_max_blocks());
|
||||||
|
ASSERT_TRUE(transfer_list.commands().empty());
|
||||||
|
}
|
167
tests/unit/edify_test.cpp
Normal file
167
tests/unit/edify_test.cpp
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
|
||||||
|
static void expect(const std::string& expr_str, const char* expected) {
|
||||||
|
std::unique_ptr<Expr> e;
|
||||||
|
int error_count = 0;
|
||||||
|
EXPECT_EQ(0, ParseString(expr_str, &e, &error_count));
|
||||||
|
EXPECT_EQ(0, error_count);
|
||||||
|
|
||||||
|
State state(expr_str, nullptr);
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
bool status = Evaluate(&state, e, &result);
|
||||||
|
|
||||||
|
if (expected == nullptr) {
|
||||||
|
EXPECT_FALSE(status);
|
||||||
|
} else {
|
||||||
|
EXPECT_STREQ(expected, result.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EdifyTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() {
|
||||||
|
RegisterBuiltins();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, parsing) {
|
||||||
|
expect("a", "a");
|
||||||
|
expect("\"a\"", "a");
|
||||||
|
expect("\"\\x61\"", "a");
|
||||||
|
expect("# this is a comment\n"
|
||||||
|
" a\n"
|
||||||
|
" \n",
|
||||||
|
"a");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, sequence) {
|
||||||
|
// sequence operator
|
||||||
|
expect("a; b; c", "c");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, concat) {
|
||||||
|
// string concat operator
|
||||||
|
expect("a + b", "ab");
|
||||||
|
expect("a + \n \"b\"", "ab");
|
||||||
|
expect("a + b +\nc\n", "abc");
|
||||||
|
|
||||||
|
// string concat function
|
||||||
|
expect("concat(a, b)", "ab");
|
||||||
|
expect("concat(a,\n \"b\")", "ab");
|
||||||
|
expect("concat(a + b,\nc,\"d\")", "abcd");
|
||||||
|
expect("\"concat\"(a + b,\nc,\"d\")", "abcd");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, logical) {
|
||||||
|
// logical and
|
||||||
|
expect("a && b", "b");
|
||||||
|
expect("a && \"\"", "");
|
||||||
|
expect("\"\" && b", "");
|
||||||
|
expect("\"\" && \"\"", "");
|
||||||
|
expect("\"\" && abort()", ""); // test short-circuiting
|
||||||
|
expect("t && abort()", nullptr);
|
||||||
|
|
||||||
|
// logical or
|
||||||
|
expect("a || b", "a");
|
||||||
|
expect("a || \"\"", "a");
|
||||||
|
expect("\"\" || b", "b");
|
||||||
|
expect("\"\" || \"\"", "");
|
||||||
|
expect("a || abort()", "a"); // test short-circuiting
|
||||||
|
expect("\"\" || abort()", NULL);
|
||||||
|
|
||||||
|
// logical not
|
||||||
|
expect("!a", "");
|
||||||
|
expect("! \"\"", "t");
|
||||||
|
expect("!!a", "t");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, precedence) {
|
||||||
|
// precedence
|
||||||
|
expect("\"\" == \"\" && b", "b");
|
||||||
|
expect("a + b == ab", "t");
|
||||||
|
expect("ab == a + b", "t");
|
||||||
|
expect("a + (b == ab)", "a");
|
||||||
|
expect("(ab == a) + b", "b");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, substring) {
|
||||||
|
// substring function
|
||||||
|
expect("is_substring(cad, abracadabra)", "t");
|
||||||
|
expect("is_substring(abrac, abracadabra)", "t");
|
||||||
|
expect("is_substring(dabra, abracadabra)", "t");
|
||||||
|
expect("is_substring(cad, abracxadabra)", "");
|
||||||
|
expect("is_substring(abrac, axbracadabra)", "");
|
||||||
|
expect("is_substring(dabra, abracadabrxa)", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, ifelse) {
|
||||||
|
// ifelse function
|
||||||
|
expect("ifelse(t, yes, no)", "yes");
|
||||||
|
expect("ifelse(!t, yes, no)", "no");
|
||||||
|
expect("ifelse(t, yes, abort())", "yes");
|
||||||
|
expect("ifelse(!t, abort(), no)", "no");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, if_statement) {
|
||||||
|
// if "statements"
|
||||||
|
expect("if t then yes else no endif", "yes");
|
||||||
|
expect("if \"\" then yes else no endif", "no");
|
||||||
|
expect("if \"\" then yes endif", "");
|
||||||
|
expect("if \"\"; t then yes endif", "yes");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, comparison) {
|
||||||
|
// numeric comparisons
|
||||||
|
expect("less_than_int(3, 14)", "t");
|
||||||
|
expect("less_than_int(14, 3)", "");
|
||||||
|
expect("less_than_int(x, 3)", "");
|
||||||
|
expect("less_than_int(3, x)", "");
|
||||||
|
expect("greater_than_int(3, 14)", "");
|
||||||
|
expect("greater_than_int(14, 3)", "t");
|
||||||
|
expect("greater_than_int(x, 3)", "");
|
||||||
|
expect("greater_than_int(3, x)", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, big_string) {
|
||||||
|
expect(std::string(8192, 's'), std::string(8192, 's').c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(EdifyTest, unknown_function) {
|
||||||
|
const char* script1 = "unknown_function()";
|
||||||
|
std::unique_ptr<Expr> expr;
|
||||||
|
int error_count = 0;
|
||||||
|
EXPECT_EQ(1, ParseString(script1, &expr, &error_count));
|
||||||
|
EXPECT_EQ(1, error_count);
|
||||||
|
|
||||||
|
const char* script2 = "abc; unknown_function()";
|
||||||
|
error_count = 0;
|
||||||
|
EXPECT_EQ(1, ParseString(script2, &expr, &error_count));
|
||||||
|
EXPECT_EQ(1, error_count);
|
||||||
|
|
||||||
|
const char* script3 = "unknown_function1() || yes";
|
||||||
|
error_count = 0;
|
||||||
|
EXPECT_EQ(1, ParseString(script3, &expr, &error_count));
|
||||||
|
EXPECT_EQ(1, error_count);
|
||||||
|
}
|
1113
tests/unit/host/imgdiff_test.cpp
Normal file
1113
tests/unit/host/imgdiff_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
403
tests/unit/host/update_simulator_test.cpp
Normal file
403
tests/unit/host/update_simulator_test.cpp
Normal file
|
@ -0,0 +1,403 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <bsdiff/bsdiff.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <ziparchive/zip_writer.h>
|
||||||
|
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
#include "updater/blockimg.h"
|
||||||
|
#include "updater/build_info.h"
|
||||||
|
#include "updater/install.h"
|
||||||
|
#include "updater/simulator_runtime.h"
|
||||||
|
#include "updater/target_files.h"
|
||||||
|
#include "updater/updater.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 &&
|
||||||
|
// hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img
|
||||||
|
// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get
|
||||||
|
// the full image.
|
||||||
|
constexpr uint8_t SPARSE_SYSTEM_HEADER[] = {
|
||||||
|
0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00,
|
||||||
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca,
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65,
|
||||||
|
0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void AddZipEntries(int fd, const std::map<string, string>& entries) {
|
||||||
|
FILE* zip_file = fdopen(fd, "w");
|
||||||
|
ZipWriter writer(zip_file);
|
||||||
|
for (const auto& pair : entries) {
|
||||||
|
ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0));
|
||||||
|
ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size()));
|
||||||
|
ASSERT_EQ(0, writer.FinishEntry());
|
||||||
|
}
|
||||||
|
ASSERT_EQ(0, writer.Finish());
|
||||||
|
ASSERT_EQ(0, fclose(zip_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
static string CalculateSha1(const string& data) {
|
||||||
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
|
||||||
|
return print_sha1(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) {
|
||||||
|
TemporaryFile patch_file;
|
||||||
|
ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(),
|
||||||
|
reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(),
|
||||||
|
patch_file.path, nullptr));
|
||||||
|
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) {
|
||||||
|
TemporaryFile cmd_pipe;
|
||||||
|
TemporaryFile temp_saved_source;
|
||||||
|
TemporaryFile temp_last_command;
|
||||||
|
TemporaryDir temp_stash_base;
|
||||||
|
|
||||||
|
Paths::Get().set_cache_temp_source(temp_saved_source.path);
|
||||||
|
Paths::Get().set_last_command_file(temp_last_command.path);
|
||||||
|
Paths::Get().set_stash_directory_base(temp_stash_base.path);
|
||||||
|
|
||||||
|
// Configure edify's functions.
|
||||||
|
RegisterBuiltins();
|
||||||
|
RegisterInstallFunctions();
|
||||||
|
RegisterBlockImageFunctions();
|
||||||
|
|
||||||
|
// Run the update simulation and check the result.
|
||||||
|
TemporaryDir work_dir;
|
||||||
|
BuildInfo build_info(work_dir.path, false);
|
||||||
|
ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false));
|
||||||
|
Updater updater(std::make_unique<SimulatorRuntime>(&build_info));
|
||||||
|
ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false));
|
||||||
|
ASSERT_EQ(expected, updater.RunUpdate());
|
||||||
|
// TODO(xunchang) check the recovery&system has the expected contents.
|
||||||
|
}
|
||||||
|
|
||||||
|
class DISABLED_UpdateSimulatorTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
std::vector<string> props = {
|
||||||
|
"import /oem/oem.prop oem*",
|
||||||
|
"# begin build properties",
|
||||||
|
"# autogenerated by buildinfo.sh",
|
||||||
|
"ro.build.id=OPR1.170510.001",
|
||||||
|
"ro.build.display.id=OPR1.170510.001 dev-keys",
|
||||||
|
"ro.build.version.incremental=3993052",
|
||||||
|
"ro.build.version.release=O",
|
||||||
|
"ro.build.date=Wed May 10 11:10:29 UTC 2017",
|
||||||
|
"ro.build.date.utc=1494414629",
|
||||||
|
"ro.build.type=user",
|
||||||
|
"ro.build.tags=dev-keys",
|
||||||
|
"ro.build.flavor=angler-user",
|
||||||
|
"ro.product.system.brand=google",
|
||||||
|
"ro.product.system.name=angler",
|
||||||
|
"ro.product.system.device=angler",
|
||||||
|
};
|
||||||
|
build_prop_string_ = android::base::Join(props, "\n");
|
||||||
|
|
||||||
|
fstab_content_ = R"(
|
||||||
|
#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
|
||||||
|
# More comments.....
|
||||||
|
|
||||||
|
/dev/block/by-name/system /system ext4 ro,barrier=1 wait
|
||||||
|
/dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata
|
||||||
|
/dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check
|
||||||
|
/dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait
|
||||||
|
/dev/block/by-name/boot /boot emmc defaults defaults
|
||||||
|
/dev/block/by-name/recovery /recovery emmc defaults defaults
|
||||||
|
/dev/block/by-name/misc /misc emmc defaults
|
||||||
|
/dev/block/by-name/modem /modem emmc defaults defaults)";
|
||||||
|
|
||||||
|
raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total
|
||||||
|
sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) +
|
||||||
|
string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
string build_prop_string_;
|
||||||
|
string fstab_content_;
|
||||||
|
string raw_system_string_;
|
||||||
|
string sparse_system_string_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(DISABLED_UpdateSimulatorTest, TargetFile_ExtractImage) {
|
||||||
|
TemporaryFile zip_file;
|
||||||
|
AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||||
|
{ "IMAGES/system.img", sparse_system_string_ } });
|
||||||
|
TargetFile target_file(zip_file.path, false);
|
||||||
|
ASSERT_TRUE(target_file.Open());
|
||||||
|
|
||||||
|
TemporaryDir temp_dir;
|
||||||
|
TemporaryFile raw_image;
|
||||||
|
ASSERT_TRUE(target_file.ExtractImage(
|
||||||
|
"IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image));
|
||||||
|
|
||||||
|
// Check the raw image has expected contents.
|
||||||
|
string content;
|
||||||
|
ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content));
|
||||||
|
string expected_content = "system.img" + string(4086, '\0');
|
||||||
|
ASSERT_EQ(expected_content, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DISABLED_UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
|
||||||
|
TemporaryFile zip_file;
|
||||||
|
AddZipEntries(zip_file.release(),
|
||||||
|
{ { "META/misc_info.txt", "" },
|
||||||
|
{ "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } });
|
||||||
|
TargetFile target_file(zip_file.path, false);
|
||||||
|
ASSERT_TRUE(target_file.Open());
|
||||||
|
|
||||||
|
std::vector<FstabInfo> fstab_info;
|
||||||
|
EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info));
|
||||||
|
|
||||||
|
std::vector<std::vector<string>> transformed;
|
||||||
|
std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed),
|
||||||
|
[](const FstabInfo& info) {
|
||||||
|
return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type };
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<std::vector<string>> expected = {
|
||||||
|
{ "/dev/block/by-name/system", "/system", "ext4" },
|
||||||
|
{ "/dev/block/by-name/vendor", "/vendor", "ext4" },
|
||||||
|
{ "/dev/block/by-name/cache", "/cache", "ext4" },
|
||||||
|
{ "/dev/block/by-name/boot", "/boot", "emmc" },
|
||||||
|
{ "/dev/block/by-name/recovery", "/recovery", "emmc" },
|
||||||
|
{ "/dev/block/by-name/misc", "/misc", "emmc" },
|
||||||
|
{ "/dev/block/by-name/modem", "/modem", "emmc" },
|
||||||
|
};
|
||||||
|
EXPECT_EQ(expected, transformed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DISABLED_UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
|
||||||
|
std::map<string, string> entries = {
|
||||||
|
{ "META/misc_info.txt", "" },
|
||||||
|
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||||
|
{ "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ },
|
||||||
|
{ "IMAGES/recovery.img", "" },
|
||||||
|
{ "IMAGES/boot.img", "" },
|
||||||
|
{ "IMAGES/misc.img", "" },
|
||||||
|
{ "IMAGES/system.map", "" },
|
||||||
|
{ "IMAGES/system.img", sparse_system_string_ },
|
||||||
|
};
|
||||||
|
|
||||||
|
TemporaryFile zip_file;
|
||||||
|
AddZipEntries(zip_file.release(), entries);
|
||||||
|
|
||||||
|
TemporaryDir temp_dir;
|
||||||
|
BuildInfo build_info(temp_dir.path, false);
|
||||||
|
ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false));
|
||||||
|
|
||||||
|
std::map<string, string> expected_result = {
|
||||||
|
{ "ro.build.id", "OPR1.170510.001" },
|
||||||
|
{ "ro.build.display.id", "OPR1.170510.001 dev-keys" },
|
||||||
|
{ "ro.build.version.incremental", "3993052" },
|
||||||
|
{ "ro.build.version.release", "O" },
|
||||||
|
{ "ro.build.date", "Wed May 10 11:10:29 UTC 2017" },
|
||||||
|
{ "ro.build.date.utc", "1494414629" },
|
||||||
|
{ "ro.build.type", "user" },
|
||||||
|
{ "ro.build.tags", "dev-keys" },
|
||||||
|
{ "ro.build.flavor", "angler-user" },
|
||||||
|
{ "ro.product.brand", "google" },
|
||||||
|
{ "ro.product.name", "angler" },
|
||||||
|
{ "ro.product.device", "angler" },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [key, value] : expected_result) {
|
||||||
|
ASSERT_EQ(value, build_info.GetProperty(key, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the temp files for each block device are created successfully.
|
||||||
|
for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery",
|
||||||
|
"/dev/block/by-name/boot", "/dev/block/by-name/misc" }) {
|
||||||
|
ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateSmoke) {
|
||||||
|
string recovery_img_string = "recovery.img";
|
||||||
|
string boot_img_string = "boot.img";
|
||||||
|
|
||||||
|
std::map<string, string> src_entries{
|
||||||
|
{ "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||||
|
{ "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
|
||||||
|
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||||
|
{ "IMAGES/recovery.img", "" },
|
||||||
|
{ "IMAGES/boot.img", boot_img_string },
|
||||||
|
{ "IMAGES/system.img", sparse_system_string_ },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construct the source target-files.
|
||||||
|
TemporaryFile src_tf;
|
||||||
|
AddZipEntries(src_tf.release(), src_entries);
|
||||||
|
|
||||||
|
string recovery_from_boot;
|
||||||
|
CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot);
|
||||||
|
|
||||||
|
// Set up the apply patch commands to patch the recovery image.
|
||||||
|
string recovery_sha1 = CalculateSha1(recovery_img_string);
|
||||||
|
string boot_sha1 = CalculateSha1(boot_img_string);
|
||||||
|
string apply_patch_source_string = android::base::StringPrintf(
|
||||||
|
"EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
|
||||||
|
string apply_patch_target_string = android::base::StringPrintf(
|
||||||
|
"EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
|
||||||
|
string check_command = android::base::StringPrintf(
|
||||||
|
R"(patch_partition_check("%s", "%s") || abort("check failed");)",
|
||||||
|
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||||
|
string patch_command = android::base::StringPrintf(
|
||||||
|
R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)",
|
||||||
|
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||||
|
|
||||||
|
// Add the commands to update the system image. Test common commands:
|
||||||
|
// * getprop
|
||||||
|
// * ui_print
|
||||||
|
// * patch_partition
|
||||||
|
// * package_extract_file (single argument)
|
||||||
|
// * block_image_verify, block_image_update
|
||||||
|
string tgt_system_string = string(4096, 'a');
|
||||||
|
string system_patch;
|
||||||
|
CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch);
|
||||||
|
|
||||||
|
string tgt_system_hash = CalculateSha1(tgt_system_string);
|
||||||
|
string src_system_hash = CalculateSha1(raw_system_string_);
|
||||||
|
|
||||||
|
std::vector<std::string> transfer_list = {
|
||||||
|
"4",
|
||||||
|
"1",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(),
|
||||||
|
src_system_hash.c_str(), tgt_system_hash.c_str()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construct the updater_script.
|
||||||
|
std::vector<string> updater_commands = {
|
||||||
|
R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)",
|
||||||
|
R"(ui_print("Source: angler/OPR1.170510.001");)",
|
||||||
|
check_command,
|
||||||
|
patch_command,
|
||||||
|
R"(block_image_verify("/dev/block/by-name/system", )"
|
||||||
|
R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
|
||||||
|
R"(abort("Failed to verify system.");)",
|
||||||
|
R"(block_image_update("/dev/block/by-name/system", )"
|
||||||
|
R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
|
||||||
|
R"(abort("Failed to verify system.");)",
|
||||||
|
};
|
||||||
|
string updater_script = android::base::Join(updater_commands, '\n');
|
||||||
|
|
||||||
|
// Construct the ota update package.
|
||||||
|
std::map<string, string> ota_entries{
|
||||||
|
{ "system.new.dat", "" },
|
||||||
|
{ "system.patch.dat", system_patch },
|
||||||
|
{ "system.transfer.list", android::base::Join(transfer_list, '\n') },
|
||||||
|
{ "META-INF/com/google/android/updater-script", updater_script },
|
||||||
|
{ "patch.p", recovery_from_boot },
|
||||||
|
};
|
||||||
|
|
||||||
|
TemporaryFile ota_package;
|
||||||
|
AddZipEntries(ota_package.release(), ota_entries);
|
||||||
|
|
||||||
|
RunSimulation(src_tf.path, ota_package.path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
|
||||||
|
std::map<string, string> src_entries{
|
||||||
|
{ "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||||
|
{ "IMAGES/system.img", sparse_system_string_ },
|
||||||
|
{ "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
|
||||||
|
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||||
|
};
|
||||||
|
|
||||||
|
TemporaryFile src_tf;
|
||||||
|
AddZipEntries(src_tf.release(), src_entries);
|
||||||
|
|
||||||
|
std::map<string, string> ota_entries{
|
||||||
|
{ "system.new.dat", "" },
|
||||||
|
{ "system.patch.dat", "" },
|
||||||
|
{ "system.transfer.list", "" },
|
||||||
|
{ "META-INF/com/google/android/updater-script", R"(bad_function("");)" },
|
||||||
|
};
|
||||||
|
|
||||||
|
TemporaryFile ota_package;
|
||||||
|
AddZipEntries(ota_package.release(), ota_entries);
|
||||||
|
|
||||||
|
RunSimulation(src_tf.path, ota_package.path, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
|
||||||
|
string recovery_img_string = "recovery.img";
|
||||||
|
string boot_img_string = "boot.img";
|
||||||
|
|
||||||
|
std::map<string, string> src_entries{
|
||||||
|
{ "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||||
|
{ "IMAGES/recovery.img", "" },
|
||||||
|
{ "IMAGES/boot.img", boot_img_string },
|
||||||
|
{ "IMAGES/system.img", sparse_system_string_ },
|
||||||
|
{ "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
|
||||||
|
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||||
|
};
|
||||||
|
|
||||||
|
TemporaryFile src_tf;
|
||||||
|
AddZipEntries(src_tf.release(), src_entries);
|
||||||
|
|
||||||
|
string recovery_sha1 = CalculateSha1(recovery_img_string);
|
||||||
|
string boot_sha1 = CalculateSha1(boot_img_string);
|
||||||
|
string apply_patch_source_string = android::base::StringPrintf(
|
||||||
|
"EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
|
||||||
|
string apply_patch_target_string = android::base::StringPrintf(
|
||||||
|
"EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
|
||||||
|
string check_command = android::base::StringPrintf(
|
||||||
|
R"(patch_partition_check("%s", "%s") || abort("check failed");)",
|
||||||
|
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||||
|
string patch_command = android::base::StringPrintf(
|
||||||
|
R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)",
|
||||||
|
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||||
|
|
||||||
|
// Give an invalid recovery patch and expect the apply patch to fail.
|
||||||
|
// TODO(xunchang) check the cause code.
|
||||||
|
std::vector<string> updater_commands = {
|
||||||
|
R"(ui_print("Source: angler/OPR1.170510.001");)",
|
||||||
|
check_command,
|
||||||
|
patch_command,
|
||||||
|
};
|
||||||
|
|
||||||
|
string updater_script = android::base::Join(updater_commands, '\n');
|
||||||
|
std::map<string, string> ota_entries{
|
||||||
|
{ "system.new.dat", "" },
|
||||||
|
{ "system.patch.dat", "" },
|
||||||
|
{ "system.transfer.list", "" },
|
||||||
|
{ "META-INF/com/google/android/updater-script", updater_script },
|
||||||
|
{ "patch.p", "random string" },
|
||||||
|
};
|
||||||
|
|
||||||
|
TemporaryFile ota_package;
|
||||||
|
AddZipEntries(ota_package.release(), ota_entries);
|
||||||
|
|
||||||
|
RunSimulation(src_tf.path, ota_package.path, false);
|
||||||
|
}
|
1227
tests/unit/updater_test.cpp
Normal file
1227
tests/unit/updater_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
191
updater/Android.bp
Normal file
191
updater/Android.bp
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
cc_defaults {
|
||||||
|
name: "libupdater_static_libs",
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libapplypatch",
|
||||||
|
"libbootloader_message",
|
||||||
|
"libbspatch",
|
||||||
|
"libedify",
|
||||||
|
"libotautil",
|
||||||
|
"libext4_utils",
|
||||||
|
"libdm",
|
||||||
|
"libfec",
|
||||||
|
"libfec_rs",
|
||||||
|
"libavb",
|
||||||
|
"libverity_tree",
|
||||||
|
"liblog",
|
||||||
|
"liblp",
|
||||||
|
"libselinux",
|
||||||
|
"libsparse",
|
||||||
|
"libsquashfs_utils",
|
||||||
|
"libbrotli",
|
||||||
|
"libbz",
|
||||||
|
"libziparchive",
|
||||||
|
"libz_stable",
|
||||||
|
"libbase",
|
||||||
|
"libcrypto_utils",
|
||||||
|
"libcutils",
|
||||||
|
"libutils",
|
||||||
|
],
|
||||||
|
header_libs: [
|
||||||
|
"libgtest_prod_headers",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_defaults {
|
||||||
|
name: "libupdater_defaults",
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"recovery_defaults",
|
||||||
|
"libupdater_static_libs",
|
||||||
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"libcrypto",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_defaults {
|
||||||
|
name: "libupdater_device_defaults",
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libfs_mgr",
|
||||||
|
"libtune2fs",
|
||||||
|
|
||||||
|
"libext2_com_err",
|
||||||
|
"libext2_blkid",
|
||||||
|
"libext2_quota",
|
||||||
|
"libext2_uuid",
|
||||||
|
"libext2_e2p",
|
||||||
|
"libext2fs",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libupdater_core",
|
||||||
|
|
||||||
|
host_supported: true,
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"recovery_defaults",
|
||||||
|
"libupdater_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"blockimg.cpp",
|
||||||
|
"commands.cpp",
|
||||||
|
"install.cpp",
|
||||||
|
"mounts.cpp",
|
||||||
|
"updater.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
target: {
|
||||||
|
darwin: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
export_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libupdater_device",
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"recovery_defaults",
|
||||||
|
"libupdater_defaults",
|
||||||
|
"libupdater_device_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"dynamic_partitions.cpp",
|
||||||
|
"updater_runtime.cpp",
|
||||||
|
"updater_runtime_dynamic_partitions.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libupdater_core",
|
||||||
|
],
|
||||||
|
|
||||||
|
include_dirs: [
|
||||||
|
"external/e2fsprogs/misc",
|
||||||
|
],
|
||||||
|
|
||||||
|
export_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_host_static {
|
||||||
|
name: "libupdater_host",
|
||||||
|
|
||||||
|
defaults: [
|
||||||
|
"recovery_defaults",
|
||||||
|
"libupdater_defaults",
|
||||||
|
],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"build_info.cpp",
|
||||||
|
"dynamic_partitions.cpp",
|
||||||
|
"simulator_runtime.cpp",
|
||||||
|
"target_files.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libupdater_core",
|
||||||
|
"libfstab",
|
||||||
|
"libc++fs",
|
||||||
|
],
|
||||||
|
|
||||||
|
target: {
|
||||||
|
darwin: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
export_include_dirs: [
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_binary_host {
|
||||||
|
name: "update_host_simulator",
|
||||||
|
defaults: ["libupdater_static_libs"],
|
||||||
|
|
||||||
|
srcs: ["update_simulator_main.cpp"],
|
||||||
|
|
||||||
|
cflags: [
|
||||||
|
"-Wall",
|
||||||
|
"-Werror",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libupdater_host",
|
||||||
|
"libupdater_core",
|
||||||
|
"libcrypto_static",
|
||||||
|
"libfstab",
|
||||||
|
"libc++fs",
|
||||||
|
],
|
||||||
|
|
||||||
|
target: {
|
||||||
|
darwin: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
118
updater/Android.mk
Normal file
118
updater/Android.mk
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
# Copyright 2009 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.
|
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
tune2fs_static_libraries := \
|
||||||
|
libext2_com_err \
|
||||||
|
libext2_blkid \
|
||||||
|
libext2_quota \
|
||||||
|
libext2_uuid \
|
||||||
|
libext2_e2p \
|
||||||
|
libext2fs
|
||||||
|
|
||||||
|
updater_common_static_libraries := \
|
||||||
|
libapplypatch \
|
||||||
|
libbootloader_message \
|
||||||
|
libbspatch \
|
||||||
|
libedify \
|
||||||
|
libotautil \
|
||||||
|
libext4_utils \
|
||||||
|
libdm \
|
||||||
|
libfec \
|
||||||
|
libfec_rs \
|
||||||
|
libavb \
|
||||||
|
libverity_tree \
|
||||||
|
liblog \
|
||||||
|
liblp \
|
||||||
|
libselinux \
|
||||||
|
libsparse \
|
||||||
|
libsquashfs_utils \
|
||||||
|
libbrotli \
|
||||||
|
libbz \
|
||||||
|
libziparchive \
|
||||||
|
libz_stable \
|
||||||
|
libbase \
|
||||||
|
libcrypto_static \
|
||||||
|
libcrypto_utils \
|
||||||
|
libcutils \
|
||||||
|
libutils
|
||||||
|
|
||||||
|
|
||||||
|
# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
|
||||||
|
# named "Register_<libname>()". Here we emit a little C function that
|
||||||
|
# gets #included by updater.cpp. It calls all those registration
|
||||||
|
# functions.
|
||||||
|
# $(1): the path to the register.inc file
|
||||||
|
# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS
|
||||||
|
define generate-register-inc
|
||||||
|
$(hide) mkdir -p $(dir $(1))
|
||||||
|
$(hide) echo "" > $(1)
|
||||||
|
$(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);)
|
||||||
|
$(hide) echo "void RegisterDeviceExtensions() {" >> $(1)
|
||||||
|
$(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);)
|
||||||
|
$(hide) echo "}" >> $(1)
|
||||||
|
endef
|
||||||
|
|
||||||
|
|
||||||
|
# updater (static executable)
|
||||||
|
# ===============================
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := updater
|
||||||
|
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
|
||||||
|
LOCAL_LICENSE_CONDITIONS := notice
|
||||||
|
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := \
|
||||||
|
updater_main.cpp
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES := \
|
||||||
|
$(LOCAL_PATH)/include
|
||||||
|
|
||||||
|
LOCAL_CFLAGS := \
|
||||||
|
-Wall \
|
||||||
|
-Werror
|
||||||
|
|
||||||
|
LOCAL_STATIC_LIBRARIES := \
|
||||||
|
libupdater_device \
|
||||||
|
libupdater_core \
|
||||||
|
$(TARGET_RECOVERY_UPDATER_LIBS) \
|
||||||
|
$(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
|
||||||
|
$(updater_common_static_libraries) \
|
||||||
|
libfs_mgr \
|
||||||
|
libtune2fs \
|
||||||
|
$(tune2fs_static_libraries)
|
||||||
|
|
||||||
|
LOCAL_HEADER_LIBRARIES := libgtest_prod_headers
|
||||||
|
|
||||||
|
LOCAL_MODULE_CLASS := EXECUTABLES
|
||||||
|
inc := $(call local-generated-sources-dir)/register.inc
|
||||||
|
|
||||||
|
# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
|
||||||
|
# These libs are also linked in with updater, but we don't try to call
|
||||||
|
# any sort of registration function for these. Use this variable for
|
||||||
|
# any subsidiary static libraries required for your registered
|
||||||
|
# extension libs.
|
||||||
|
$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
|
||||||
|
$(inc) :
|
||||||
|
$(call generate-register-inc,$@,$(libs))
|
||||||
|
|
||||||
|
LOCAL_GENERATED_SOURCES := $(inc)
|
||||||
|
|
||||||
|
inc :=
|
||||||
|
|
||||||
|
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||||
|
|
||||||
|
include $(BUILD_EXECUTABLE)
|
2303
updater/blockimg.cpp
Normal file
2303
updater/blockimg.cpp
Normal file
File diff suppressed because it is too large
Load diff
139
updater/build_info.cpp
Normal file
139
updater/build_info.cpp
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "updater/build_info.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
|
||||||
|
#include "updater/target_files.h"
|
||||||
|
|
||||||
|
bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) {
|
||||||
|
TargetFile target_file(std::string(target_file_path), extracted_input);
|
||||||
|
if (!target_file.Open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target_file.GetBuildProps(&build_props_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FstabInfo> fstab_info_list;
|
||||||
|
if (!target_file.ParseFstabInfo(&fstab_info_list)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& fstab_info : fstab_info_list) {
|
||||||
|
for (const auto& directory : { "IMAGES", "RADIO" }) {
|
||||||
|
std::string entry_name = directory + fstab_info.mount_point + ".img";
|
||||||
|
if (!target_file.EntryExists(entry_name)) {
|
||||||
|
LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_files_.emplace_back(work_dir_);
|
||||||
|
auto& image_file = temp_files_.back();
|
||||||
|
if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) {
|
||||||
|
LOG(ERROR) << "Failed to set up source image files.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mapped_path = image_file.path;
|
||||||
|
// Rename the images to more readable ones if we want to keep the image.
|
||||||
|
if (keep_images_) {
|
||||||
|
mapped_path = work_dir_ + fstab_info.mount_point + ".img";
|
||||||
|
image_file.release();
|
||||||
|
if (rename(image_file.path, mapped_path.c_str()) != 0) {
|
||||||
|
PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name
|
||||||
|
<< " to " << mapped_path;
|
||||||
|
|
||||||
|
blockdev_map_.emplace(
|
||||||
|
fstab_info.blockdev_name,
|
||||||
|
FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BuildInfo::GetProperty(const std::string_view key,
|
||||||
|
const std::string_view default_value) const {
|
||||||
|
// The logic to parse the ro.product properties should be in line with the generation script.
|
||||||
|
// More details in common.py BuildInfo.GetBuildProp.
|
||||||
|
// TODO(xunchang) handle the oem property and the source order defined in
|
||||||
|
// ro.product.property_source_order
|
||||||
|
const std::set<std::string, std::less<>> ro_product_props = {
|
||||||
|
"ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model",
|
||||||
|
"ro.product.name"
|
||||||
|
};
|
||||||
|
const std::vector<std::string> source_order = {
|
||||||
|
"product", "odm", "vendor", "system_ext", "system",
|
||||||
|
};
|
||||||
|
if (ro_product_props.find(key) != ro_product_props.end()) {
|
||||||
|
std::string_view key_suffix(key);
|
||||||
|
CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product"));
|
||||||
|
for (const auto& source : source_order) {
|
||||||
|
std::string resolved_key = "ro.product." + source + std::string(key_suffix);
|
||||||
|
if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) {
|
||||||
|
return entry->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG(WARNING) << "Failed to find property: " << key;
|
||||||
|
return std::string(default_value);
|
||||||
|
} else if (key == "ro.build.fingerprint") {
|
||||||
|
// clang-format off
|
||||||
|
return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s",
|
||||||
|
GetProperty("ro.product.brand", "").c_str(),
|
||||||
|
GetProperty("ro.product.name", "").c_str(),
|
||||||
|
GetProperty("ro.product.device", "").c_str(),
|
||||||
|
GetProperty("ro.build.version.release", "").c_str(),
|
||||||
|
GetProperty("ro.build.id", "").c_str(),
|
||||||
|
GetProperty("ro.build.version.incremental", "").c_str(),
|
||||||
|
GetProperty("ro.build.type", "").c_str(),
|
||||||
|
GetProperty("ro.build.tags", "").c_str());
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entry = build_props_.find(key);
|
||||||
|
if (entry == build_props_.end()) {
|
||||||
|
LOG(WARNING) << "Failed to find property: " << key;
|
||||||
|
return std::string(default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const {
|
||||||
|
auto entry = blockdev_map_.find(name);
|
||||||
|
if (entry == blockdev_map_.end()) {
|
||||||
|
LOG(WARNING) << "Failed to find path to block device " << name;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry->second.mounted_file_path;
|
||||||
|
}
|
453
updater/commands.cpp
Normal file
453
updater/commands.cpp
Normal file
|
@ -0,0 +1,453 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "private/commands.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
#include "otautil/rangeset.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
bool Command::abort_allowed_ = false;
|
||||||
|
|
||||||
|
Command::Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info)
|
||||||
|
: type_(type),
|
||||||
|
index_(index),
|
||||||
|
cmdline_(std::move(cmdline)),
|
||||||
|
hash_tree_info_(std::move(hash_tree_info)) {
|
||||||
|
CHECK(type == Type::COMPUTE_HASH_TREE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Type Command::ParseType(const std::string& type_str) {
|
||||||
|
if (type_str == "abort") {
|
||||||
|
if (!abort_allowed_) {
|
||||||
|
LOG(ERROR) << "ABORT disallowed";
|
||||||
|
return Type::LAST;
|
||||||
|
}
|
||||||
|
return Type::ABORT;
|
||||||
|
} else if (type_str == "bsdiff") {
|
||||||
|
return Type::BSDIFF;
|
||||||
|
} else if (type_str == "compute_hash_tree") {
|
||||||
|
return Type::COMPUTE_HASH_TREE;
|
||||||
|
} else if (type_str == "erase") {
|
||||||
|
return Type::ERASE;
|
||||||
|
} else if (type_str == "free") {
|
||||||
|
return Type::FREE;
|
||||||
|
} else if (type_str == "imgdiff") {
|
||||||
|
return Type::IMGDIFF;
|
||||||
|
} else if (type_str == "move") {
|
||||||
|
return Type::MOVE;
|
||||||
|
} else if (type_str == "new") {
|
||||||
|
return Type::NEW;
|
||||||
|
} else if (type_str == "stash") {
|
||||||
|
return Type::STASH;
|
||||||
|
} else if (type_str == "zero") {
|
||||||
|
return Type::ZERO;
|
||||||
|
}
|
||||||
|
return Type::LAST;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Command::ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
|
||||||
|
const std::string& tgt_hash, TargetInfo* target,
|
||||||
|
const std::string& src_hash, SourceInfo* source,
|
||||||
|
std::string* err) {
|
||||||
|
// We expect the given args (in 'tokens' vector) in one of the following formats.
|
||||||
|
//
|
||||||
|
// <tgt_ranges> <src_block_count> - <[stash_id:location] ...>
|
||||||
|
// (loads data from stashes only)
|
||||||
|
//
|
||||||
|
// <tgt_ranges> <src_block_count> <src_ranges>
|
||||||
|
// (loads data from source image only)
|
||||||
|
//
|
||||||
|
// <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location> <[stash_id:location] ...>
|
||||||
|
// (loads data from both of source image and stashes)
|
||||||
|
|
||||||
|
// At least it needs to provide three args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>.
|
||||||
|
if (tokens.size() < 3) {
|
||||||
|
*err = "invalid number of args";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!tgt_ranges) {
|
||||||
|
*err = "invalid target ranges";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*target = TargetInfo(tgt_hash, tgt_ranges);
|
||||||
|
|
||||||
|
// <src_block_count>
|
||||||
|
const std::string& token = tokens[pos++];
|
||||||
|
size_t src_blocks;
|
||||||
|
if (!android::base::ParseUint(token, &src_blocks)) {
|
||||||
|
*err = "invalid src_block_count \""s + token + "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeSet src_ranges;
|
||||||
|
RangeSet src_ranges_location;
|
||||||
|
// "-" or <src_ranges> [<src_ranges_location>]
|
||||||
|
if (tokens[pos] == "-") {
|
||||||
|
// no source ranges, only stashes
|
||||||
|
pos++;
|
||||||
|
} else {
|
||||||
|
src_ranges = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!src_ranges) {
|
||||||
|
*err = "invalid source ranges";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos >= tokens.size()) {
|
||||||
|
// No stashes, only source ranges.
|
||||||
|
SourceInfo result(src_hash, src_ranges, {}, {});
|
||||||
|
|
||||||
|
if (result.blocks() != src_blocks) {
|
||||||
|
*err =
|
||||||
|
android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
|
||||||
|
src_ranges.ToString().c_str(), src_blocks);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*source = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
src_ranges_location = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!src_ranges_location) {
|
||||||
|
*err = "invalid source ranges location";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// <[stash_id:stash_location]>
|
||||||
|
std::vector<StashInfo> stashes;
|
||||||
|
while (pos < tokens.size()) {
|
||||||
|
// Each word is a an index into the stash table, a colon, and then a RangeSet describing where
|
||||||
|
// in the source block that stashed data should go.
|
||||||
|
std::vector<std::string> pairs = android::base::Split(tokens[pos++], ":");
|
||||||
|
if (pairs.size() != 2) {
|
||||||
|
*err = "invalid stash info";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RangeSet stash_location = RangeSet::Parse(pairs[1]);
|
||||||
|
if (!stash_location) {
|
||||||
|
*err = "invalid stash location";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stashes.emplace_back(pairs[0], stash_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes);
|
||||||
|
if (src_blocks != result.blocks()) {
|
||||||
|
*err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(),
|
||||||
|
src_ranges.ToString().c_str(), src_blocks);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*source = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command Command::Parse(const std::string& line, size_t index, std::string* err) {
|
||||||
|
std::vector<std::string> tokens = android::base::Split(line, " ");
|
||||||
|
size_t pos = 0;
|
||||||
|
// tokens.size() will be 1 at least.
|
||||||
|
Type op = ParseType(tokens[pos++]);
|
||||||
|
if (op == Type::LAST) {
|
||||||
|
*err = "invalid type";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchInfo patch_info;
|
||||||
|
TargetInfo target_info;
|
||||||
|
SourceInfo source_info;
|
||||||
|
StashInfo stash_info;
|
||||||
|
|
||||||
|
if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) {
|
||||||
|
// zero/new/erase <rangeset>
|
||||||
|
if (pos + 1 != tokens.size()) {
|
||||||
|
*err = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
|
||||||
|
tokens.size() - pos);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!tgt_ranges) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
static const std::string kUnknownHash{ "unknown-hash" };
|
||||||
|
target_info = TargetInfo(kUnknownHash, tgt_ranges);
|
||||||
|
} else if (op == Type::STASH) {
|
||||||
|
// stash <stash_id> <src_ranges>
|
||||||
|
if (pos + 2 != tokens.size()) {
|
||||||
|
*err = android::base::StringPrintf("invalid number of args: %zu (expected 2)",
|
||||||
|
tokens.size() - pos);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const std::string& id = tokens[pos++];
|
||||||
|
RangeSet src_ranges = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!src_ranges) {
|
||||||
|
*err = "invalid token";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
stash_info = StashInfo(id, src_ranges);
|
||||||
|
} else if (op == Type::FREE) {
|
||||||
|
// free <stash_id>
|
||||||
|
if (pos + 1 != tokens.size()) {
|
||||||
|
*err = android::base::StringPrintf("invalid number of args: %zu (expected 1)",
|
||||||
|
tokens.size() - pos);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
stash_info = StashInfo(tokens[pos++], {});
|
||||||
|
} else if (op == Type::MOVE) {
|
||||||
|
// <hash>
|
||||||
|
if (pos + 1 > tokens.size()) {
|
||||||
|
*err = "missing hash";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::string hash = tokens[pos++];
|
||||||
|
if (!ParseTargetInfoAndSourceInfo(
|
||||||
|
std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), hash, &target_info,
|
||||||
|
hash, &source_info, err)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else if (op == Type::BSDIFF || op == Type::IMGDIFF) {
|
||||||
|
// <offset> <length> <srchash> <dsthash>
|
||||||
|
if (pos + 4 > tokens.size()) {
|
||||||
|
*err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)",
|
||||||
|
tokens.size() - pos);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
size_t offset;
|
||||||
|
size_t length;
|
||||||
|
if (!android::base::ParseUint(tokens[pos++], &offset) ||
|
||||||
|
!android::base::ParseUint(tokens[pos++], &length)) {
|
||||||
|
*err = "invalid patch offset/length";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
patch_info = PatchInfo(offset, length);
|
||||||
|
|
||||||
|
std::string src_hash = tokens[pos++];
|
||||||
|
std::string dst_hash = tokens[pos++];
|
||||||
|
if (!ParseTargetInfoAndSourceInfo(
|
||||||
|
std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info,
|
||||||
|
src_hash, &source_info, err)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else if (op == Type::ABORT) {
|
||||||
|
// Abort takes no arguments, so there's nothing else to check.
|
||||||
|
if (pos != tokens.size()) {
|
||||||
|
*err = android::base::StringPrintf("invalid number of args: %zu (expected 0)",
|
||||||
|
tokens.size() - pos);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else if (op == Type::COMPUTE_HASH_TREE) {
|
||||||
|
// <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
|
||||||
|
if (pos + 5 != tokens.size()) {
|
||||||
|
*err = android::base::StringPrintf("invalid number of args: %zu (expected 5)",
|
||||||
|
tokens.size() - pos);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expects the hash_tree data to be contiguous.
|
||||||
|
RangeSet hash_tree_ranges = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!hash_tree_ranges || hash_tree_ranges.size() != 1) {
|
||||||
|
*err = "invalid hash tree ranges in: " + line;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeSet source_ranges = RangeSet::Parse(tokens[pos++]);
|
||||||
|
if (!source_ranges) {
|
||||||
|
*err = "invalid source ranges in: " + line;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string hash_algorithm = tokens[pos++];
|
||||||
|
std::string salt_hex = tokens[pos++];
|
||||||
|
std::string root_hash = tokens[pos++];
|
||||||
|
if (hash_algorithm.empty() || salt_hex.empty() || root_hash.empty()) {
|
||||||
|
*err = "invalid hash tree arguments in " + line;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
HashTreeInfo hash_tree_info(std::move(hash_tree_ranges), std::move(source_ranges),
|
||||||
|
std::move(hash_algorithm), std::move(salt_hex),
|
||||||
|
std::move(root_hash));
|
||||||
|
return Command(op, index, line, std::move(hash_tree_info));
|
||||||
|
} else {
|
||||||
|
*err = "invalid op";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command(op, index, line, patch_info, target_info, source_info, stash_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SourceInfo::Overlaps(const TargetInfo& target) const {
|
||||||
|
return ranges_.Overlaps(target.ranges());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moves blocks in the 'source' vector to the specified locations (as in 'locs') in the 'dest'
|
||||||
|
// vector. Note that source and dest may be the same buffer.
|
||||||
|
static void MoveRange(std::vector<uint8_t>* dest, const RangeSet& locs,
|
||||||
|
const std::vector<uint8_t>& source, size_t block_size) {
|
||||||
|
const uint8_t* from = source.data();
|
||||||
|
uint8_t* to = dest->data();
|
||||||
|
size_t start = locs.blocks();
|
||||||
|
// Must do the movement backward.
|
||||||
|
for (auto it = locs.crbegin(); it != locs.crend(); it++) {
|
||||||
|
size_t blocks = it->second - it->first;
|
||||||
|
start -= blocks;
|
||||||
|
memmove(to + (it->first * block_size), from + (start * block_size), blocks * block_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SourceInfo::ReadAll(
|
||||||
|
std::vector<uint8_t>* buffer, size_t block_size,
|
||||||
|
const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader,
|
||||||
|
const std::function<int(const std::string&, std::vector<uint8_t>*)>& stash_reader) const {
|
||||||
|
if (buffer->size() < blocks() * block_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in the source ranges.
|
||||||
|
if (ranges_) {
|
||||||
|
if (block_reader(ranges_, buffer) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (location_) {
|
||||||
|
MoveRange(buffer, location_, *buffer, block_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in the stashes.
|
||||||
|
for (const StashInfo& stash : stashes_) {
|
||||||
|
std::vector<uint8_t> stash_buffer(stash.blocks() * block_size);
|
||||||
|
if (stash_reader(stash.id(), &stash_buffer) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MoveRange(buffer, stash.ranges(), stash_buffer, block_size);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceInfo::DumpBuffer(const std::vector<uint8_t>& buffer, size_t block_size) const {
|
||||||
|
LOG(INFO) << "Dumping hashes in hex for " << ranges_.blocks() << " source blocks";
|
||||||
|
|
||||||
|
const RangeSet& location = location_ ? location_ : RangeSet({ Range{ 0, ranges_.blocks() } });
|
||||||
|
for (size_t i = 0; i < ranges_.blocks(); i++) {
|
||||||
|
size_t block_num = ranges_.GetBlockNumber(i);
|
||||||
|
size_t buffer_index = location.GetBlockNumber(i);
|
||||||
|
CHECK_LE((buffer_index + 1) * block_size, buffer.size());
|
||||||
|
|
||||||
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1(buffer.data() + buffer_index * block_size, block_size, digest);
|
||||||
|
std::string hexdigest = print_sha1(digest);
|
||||||
|
LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const Command& command) {
|
||||||
|
os << command.index() << ": " << command.cmdline();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const TargetInfo& target) {
|
||||||
|
os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const StashInfo& stash) {
|
||||||
|
os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const SourceInfo& source) {
|
||||||
|
os << source.blocks_ << " blocks (" << source.hash_ << "): ";
|
||||||
|
if (source.ranges_) {
|
||||||
|
os << source.ranges_.ToString();
|
||||||
|
if (source.location_) {
|
||||||
|
os << " (location: " << source.location_.ToString() << ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!source.stashes_.empty()) {
|
||||||
|
os << " " << source.stashes_.size() << " stash(es)";
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransferList TransferList::Parse(const std::string& transfer_list_str, std::string* err) {
|
||||||
|
TransferList result{};
|
||||||
|
|
||||||
|
std::vector<std::string> lines = android::base::Split(transfer_list_str, "\n");
|
||||||
|
if (lines.size() < kTransferListHeaderLines) {
|
||||||
|
*err = android::base::StringPrintf("too few lines in the transfer list [%zu]", lines.size());
|
||||||
|
return TransferList{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// First line in transfer list is the version number.
|
||||||
|
if (!android::base::ParseInt(lines[0], &result.version_, 3, 4)) {
|
||||||
|
*err = "unexpected transfer list version ["s + lines[0] + "]";
|
||||||
|
return TransferList{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second line in transfer list is the total number of blocks we expect to write.
|
||||||
|
if (!android::base::ParseUint(lines[1], &result.total_blocks_)) {
|
||||||
|
*err = "unexpected block count ["s + lines[1] + "]";
|
||||||
|
return TransferList{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third line is how many stash entries are needed simultaneously.
|
||||||
|
if (!android::base::ParseUint(lines[2], &result.stash_max_entries_)) {
|
||||||
|
return TransferList{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth line is the maximum number of blocks that will be stashed simultaneously.
|
||||||
|
if (!android::base::ParseUint(lines[3], &result.stash_max_blocks_)) {
|
||||||
|
*err = "unexpected maximum stash blocks ["s + lines[3] + "]";
|
||||||
|
return TransferList{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subsequent lines are all individual transfer commands.
|
||||||
|
for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) {
|
||||||
|
const std::string& line = lines[i];
|
||||||
|
if (line.empty()) continue;
|
||||||
|
|
||||||
|
size_t cmdindex = i - kTransferListHeaderLines;
|
||||||
|
std::string parsing_error;
|
||||||
|
Command command = Command::Parse(line, cmdindex, &parsing_error);
|
||||||
|
if (!command) {
|
||||||
|
*err = android::base::StringPrintf("Failed to parse command %zu [%s]: %s", cmdindex,
|
||||||
|
line.c_str(), parsing_error.c_str());
|
||||||
|
return TransferList{};
|
||||||
|
}
|
||||||
|
result.commands_.push_back(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
140
updater/dynamic_partitions.cpp
Normal file
140
updater/dynamic_partitions.cpp
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "updater/dynamic_partitions.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "edify/updater_runtime_interface.h"
|
||||||
|
#include "otautil/error_code.h"
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
#include "private/utils.h"
|
||||||
|
|
||||||
|
static std::vector<std::string> ReadStringArgs(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv,
|
||||||
|
const std::vector<std::string>& arg_names) {
|
||||||
|
if (argv.size() != arg_names.size()) {
|
||||||
|
ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name,
|
||||||
|
arg_names.size(), argv.size());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Value>> args;
|
||||||
|
if (!ReadValueArgs(state, argv, &args)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(args.size(), arg_names.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arg_names.size(); ++i) {
|
||||||
|
if (args[i]->type != Value::Type::STRING) {
|
||||||
|
ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string",
|
||||||
|
arg_names[i].c_str(), name);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ret;
|
||||||
|
std::transform(args.begin(), args.end(), std::back_inserter(ret),
|
||||||
|
[](const auto& arg) { return arg->data; });
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* UnmapPartitionFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
auto args = ReadStringArgs(name, state, argv, { "name" });
|
||||||
|
if (args.empty()) return StringValue("");
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t")
|
||||||
|
: StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* MapPartitionFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
auto args = ReadStringArgs(name, state, argv, { "name" });
|
||||||
|
if (args.empty()) return StringValue("");
|
||||||
|
|
||||||
|
std::string path;
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path);
|
||||||
|
return result ? StringValue(path) : StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED";
|
||||||
|
|
||||||
|
Value* UpdateDynamicPartitionsFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size());
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
std::vector<std::unique_ptr<Value>> args;
|
||||||
|
if (!ReadValueArgs(state, argv, &args)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const std::unique_ptr<Value>& op_list_value = args[0];
|
||||||
|
if (op_list_value->type != Value::Type::BLOB) {
|
||||||
|
ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name);
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker;
|
||||||
|
if (state->is_retry) {
|
||||||
|
struct stat sb;
|
||||||
|
int result = stat(updated_marker.c_str(), &sb);
|
||||||
|
if (result == 0) {
|
||||||
|
LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker";
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delete the obsolete marker if any.
|
||||||
|
std::string err;
|
||||||
|
if (!android::base::RemoveFileIfExists(updated_marker, &err)) {
|
||||||
|
LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker
|
||||||
|
<< ": " << err;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) {
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetUpdatedMarker(updated_marker)) {
|
||||||
|
LOG(ERROR) << "Failed to set metadata updated marker.";
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterDynamicPartitionsFunctions() {
|
||||||
|
RegisterFunction("unmap_partition", UnmapPartitionFn);
|
||||||
|
RegisterFunction("map_partition", MapPartitionFn);
|
||||||
|
RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn);
|
||||||
|
}
|
475
updater/include/private/commands.h
Normal file
475
updater/include/private/commands.h
Normal file
|
@ -0,0 +1,475 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <gtest/gtest_prod.h> // FRIEND_TEST
|
||||||
|
|
||||||
|
#include "otautil/rangeset.h"
|
||||||
|
|
||||||
|
// Represents the target info used in a Command. TargetInfo contains the ranges of the blocks and
|
||||||
|
// the expected hash.
|
||||||
|
class TargetInfo {
|
||||||
|
public:
|
||||||
|
TargetInfo() = default;
|
||||||
|
|
||||||
|
TargetInfo(std::string hash, RangeSet ranges)
|
||||||
|
: hash_(std::move(hash)), ranges_(std::move(ranges)) {}
|
||||||
|
|
||||||
|
const std::string& hash() const {
|
||||||
|
return hash_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RangeSet& ranges() const {
|
||||||
|
return ranges_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t blocks() const {
|
||||||
|
return ranges_.blocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const TargetInfo& other) const {
|
||||||
|
return hash_ == other.hash_ && ranges_ == other.ranges_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const TargetInfo& source);
|
||||||
|
|
||||||
|
// The hash of the data represented by the object.
|
||||||
|
std::string hash_;
|
||||||
|
// The block ranges that the data should be written to.
|
||||||
|
RangeSet ranges_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const TargetInfo& source);
|
||||||
|
|
||||||
|
// Represents the stash info used in a Command.
|
||||||
|
class StashInfo {
|
||||||
|
public:
|
||||||
|
StashInfo() = default;
|
||||||
|
|
||||||
|
StashInfo(std::string id, RangeSet ranges) : id_(std::move(id)), ranges_(std::move(ranges)) {}
|
||||||
|
|
||||||
|
size_t blocks() const {
|
||||||
|
return ranges_.blocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& id() const {
|
||||||
|
return id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RangeSet& ranges() const {
|
||||||
|
return ranges_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const StashInfo& other) const {
|
||||||
|
return id_ == other.id_ && ranges_ == other.ranges_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const StashInfo& stash);
|
||||||
|
|
||||||
|
// The id (i.e. hash) of the stash.
|
||||||
|
std::string id_;
|
||||||
|
// The matching location of the stash.
|
||||||
|
RangeSet ranges_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const StashInfo& stash);
|
||||||
|
|
||||||
|
// Represents the source info in a Command, whose data could come from source image, stashed blocks,
|
||||||
|
// or both.
|
||||||
|
class SourceInfo {
|
||||||
|
public:
|
||||||
|
SourceInfo() = default;
|
||||||
|
|
||||||
|
SourceInfo(std::string hash, RangeSet ranges, RangeSet location, std::vector<StashInfo> stashes)
|
||||||
|
: hash_(std::move(hash)),
|
||||||
|
ranges_(std::move(ranges)),
|
||||||
|
location_(std::move(location)),
|
||||||
|
stashes_(std::move(stashes)) {
|
||||||
|
blocks_ = ranges_.blocks();
|
||||||
|
for (const auto& stash : stashes_) {
|
||||||
|
blocks_ += stash.ranges().blocks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads all the data specified by this SourceInfo object into the given 'buffer', by calling the
|
||||||
|
// given readers. Caller needs to specify the block size for the represented blocks. The given
|
||||||
|
// buffer needs to be sufficiently large. Otherwise it returns false. 'block_reader' and
|
||||||
|
// 'stash_reader' read the specified data into the given buffer (guaranteed to be large enough)
|
||||||
|
// respectively. The readers should return 0 on success, or -1 on error.
|
||||||
|
bool ReadAll(
|
||||||
|
std::vector<uint8_t>* buffer, size_t block_size,
|
||||||
|
const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader,
|
||||||
|
const std::function<int(const std::string&, std::vector<uint8_t>*)>& stash_reader) const;
|
||||||
|
|
||||||
|
// Whether this SourceInfo overlaps with the given TargetInfo object.
|
||||||
|
bool Overlaps(const TargetInfo& target) const;
|
||||||
|
|
||||||
|
// Dumps the hashes in hex for the given buffer that's loaded from this SourceInfo object
|
||||||
|
// (excluding the stashed blocks which are handled separately).
|
||||||
|
void DumpBuffer(const std::vector<uint8_t>& buffer, size_t block_size) const;
|
||||||
|
|
||||||
|
const std::string& hash() const {
|
||||||
|
return hash_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t blocks() const {
|
||||||
|
return blocks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const SourceInfo& other) const {
|
||||||
|
return hash_ == other.hash_ && ranges_ == other.ranges_ && location_ == other.location_ &&
|
||||||
|
stashes_ == other.stashes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const SourceInfo& source);
|
||||||
|
|
||||||
|
// The hash of the data represented by the object.
|
||||||
|
std::string hash_;
|
||||||
|
// The block ranges from the source image to read data from. This could be a subset of all the
|
||||||
|
// blocks represented by the object, or empty if all the data should be loaded from stash.
|
||||||
|
RangeSet ranges_;
|
||||||
|
// The location in the buffer to load ranges_ into. Empty if ranges_ alone covers all the blocks
|
||||||
|
// (i.e. nothing needs to be loaded from stash).
|
||||||
|
RangeSet location_;
|
||||||
|
// The info for the stashed blocks that are part of the source. Empty if there's none.
|
||||||
|
std::vector<StashInfo> stashes_;
|
||||||
|
// Total number of blocks represented by the object.
|
||||||
|
size_t blocks_{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const SourceInfo& source);
|
||||||
|
|
||||||
|
class PatchInfo {
|
||||||
|
public:
|
||||||
|
PatchInfo() = default;
|
||||||
|
|
||||||
|
PatchInfo(size_t offset, size_t length) : offset_(offset), length_(length) {}
|
||||||
|
|
||||||
|
size_t offset() const {
|
||||||
|
return offset_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t length() const {
|
||||||
|
return length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const PatchInfo& other) const {
|
||||||
|
return offset_ == other.offset_ && length_ == other.length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t offset_{ 0 };
|
||||||
|
size_t length_{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// The arguments to build a hash tree from blocks on the block device.
|
||||||
|
class HashTreeInfo {
|
||||||
|
public:
|
||||||
|
HashTreeInfo() = default;
|
||||||
|
|
||||||
|
HashTreeInfo(RangeSet hash_tree_ranges, RangeSet source_ranges, std::string hash_algorithm,
|
||||||
|
std::string salt_hex, std::string root_hash)
|
||||||
|
: hash_tree_ranges_(std::move(hash_tree_ranges)),
|
||||||
|
source_ranges_(std::move(source_ranges)),
|
||||||
|
hash_algorithm_(std::move(hash_algorithm)),
|
||||||
|
salt_hex_(std::move(salt_hex)),
|
||||||
|
root_hash_(std::move(root_hash)) {}
|
||||||
|
|
||||||
|
const RangeSet& hash_tree_ranges() const {
|
||||||
|
return hash_tree_ranges_;
|
||||||
|
}
|
||||||
|
const RangeSet& source_ranges() const {
|
||||||
|
return source_ranges_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& hash_algorithm() const {
|
||||||
|
return hash_algorithm_;
|
||||||
|
}
|
||||||
|
const std::string& salt_hex() const {
|
||||||
|
return salt_hex_;
|
||||||
|
}
|
||||||
|
const std::string& root_hash() const {
|
||||||
|
return root_hash_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const HashTreeInfo& other) const {
|
||||||
|
return hash_tree_ranges_ == other.hash_tree_ranges_ && source_ranges_ == other.source_ranges_ &&
|
||||||
|
hash_algorithm_ == other.hash_algorithm_ && salt_hex_ == other.salt_hex_ &&
|
||||||
|
root_hash_ == other.root_hash_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
RangeSet hash_tree_ranges_;
|
||||||
|
RangeSet source_ranges_;
|
||||||
|
std::string hash_algorithm_;
|
||||||
|
std::string salt_hex_;
|
||||||
|
std::string root_hash_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Command class holds the info for an update command that performs block-based OTA (BBOTA). Each
|
||||||
|
// command consists of one or several args, namely TargetInfo, SourceInfo, StashInfo and PatchInfo.
|
||||||
|
// The currently used BBOTA version is v4.
|
||||||
|
//
|
||||||
|
// zero <tgt_ranges>
|
||||||
|
// - Fill the indicated blocks with zeros.
|
||||||
|
// - Meaningful args: TargetInfo
|
||||||
|
//
|
||||||
|
// new <tgt_ranges>
|
||||||
|
// - Fill the blocks with data read from the new_data file.
|
||||||
|
// - Meaningful args: TargetInfo
|
||||||
|
//
|
||||||
|
// erase <tgt_ranges>
|
||||||
|
// - Mark the given blocks as empty.
|
||||||
|
// - Meaningful args: TargetInfo
|
||||||
|
//
|
||||||
|
// move <hash> <...>
|
||||||
|
// - Read the source blocks, write result to target blocks.
|
||||||
|
// - Meaningful args: TargetInfo, SourceInfo
|
||||||
|
//
|
||||||
|
// See the note below for <...>.
|
||||||
|
//
|
||||||
|
// bsdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
|
||||||
|
// imgdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
|
||||||
|
// - Read the source blocks, apply a patch, and write result to target blocks.
|
||||||
|
// - Meaningful args: PatchInfo, TargetInfo, SourceInfo
|
||||||
|
//
|
||||||
|
// It expects <...> in one of the following formats:
|
||||||
|
//
|
||||||
|
// <tgt_ranges> <src_block_count> - <[stash_id:stash_location] ...>
|
||||||
|
// (loads data from stashes only)
|
||||||
|
//
|
||||||
|
// <tgt_ranges> <src_block_count> <src_ranges>
|
||||||
|
// (loads data from source image only)
|
||||||
|
//
|
||||||
|
// <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location>
|
||||||
|
// <[stash_id:stash_location] ...>
|
||||||
|
// (loads data from both of source image and stashes)
|
||||||
|
//
|
||||||
|
// stash <stash_id> <src_ranges>
|
||||||
|
// - Load the given source blocks and stash the data in the given slot of the stash table.
|
||||||
|
// - Meaningful args: StashInfo
|
||||||
|
//
|
||||||
|
// free <stash_id>
|
||||||
|
// - Free the given stash data.
|
||||||
|
// - Meaningful args: StashInfo
|
||||||
|
//
|
||||||
|
// compute_hash_tree <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
|
||||||
|
// - Computes the hash_tree bytes and writes the result to the specified range on the
|
||||||
|
// block_device.
|
||||||
|
//
|
||||||
|
// abort
|
||||||
|
// - Abort the current update. Allowed for testing code only.
|
||||||
|
//
|
||||||
|
class Command {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
ABORT,
|
||||||
|
BSDIFF,
|
||||||
|
COMPUTE_HASH_TREE,
|
||||||
|
ERASE,
|
||||||
|
FREE,
|
||||||
|
IMGDIFF,
|
||||||
|
MOVE,
|
||||||
|
NEW,
|
||||||
|
STASH,
|
||||||
|
ZERO,
|
||||||
|
LAST, // Not a valid type.
|
||||||
|
};
|
||||||
|
|
||||||
|
Command() = default;
|
||||||
|
|
||||||
|
Command(Type type, size_t index, std::string cmdline, PatchInfo patch, TargetInfo target,
|
||||||
|
SourceInfo source, StashInfo stash)
|
||||||
|
: type_(type),
|
||||||
|
index_(index),
|
||||||
|
cmdline_(std::move(cmdline)),
|
||||||
|
patch_(patch),
|
||||||
|
target_(std::move(target)),
|
||||||
|
source_(std::move(source)),
|
||||||
|
stash_(std::move(stash)) {}
|
||||||
|
|
||||||
|
Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info);
|
||||||
|
|
||||||
|
// Parses the given command 'line' into a Command object and returns it. The 'index' is specified
|
||||||
|
// by the caller to index the object. On parsing error, it returns an empty Command object that
|
||||||
|
// evaluates to false, and the specific error message will be set in 'err'.
|
||||||
|
static Command Parse(const std::string& line, size_t index, std::string* err);
|
||||||
|
|
||||||
|
// Parses the command type from the given string.
|
||||||
|
static Type ParseType(const std::string& type_str);
|
||||||
|
|
||||||
|
Type type() const {
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index() const {
|
||||||
|
return index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& cmdline() const {
|
||||||
|
return cmdline_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PatchInfo& patch() const {
|
||||||
|
return patch_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TargetInfo& target() const {
|
||||||
|
return target_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SourceInfo& source() const {
|
||||||
|
return source_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StashInfo& stash() const {
|
||||||
|
return stash_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HashTreeInfo& hash_tree_info() const {
|
||||||
|
return hash_tree_info_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t block_size() const {
|
||||||
|
return block_size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr explicit operator bool() const {
|
||||||
|
return type_ != Type::LAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ResumableUpdaterTest;
|
||||||
|
friend class UpdaterTest;
|
||||||
|
|
||||||
|
FRIEND_TEST(CommandsTest, Parse_ABORT_Allowed);
|
||||||
|
FRIEND_TEST(CommandsTest, Parse_InvalidNumberOfArgs);
|
||||||
|
FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput);
|
||||||
|
FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly);
|
||||||
|
FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes);
|
||||||
|
FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly);
|
||||||
|
|
||||||
|
// Parses the target and source info from the given 'tokens' vector. Saves the parsed info into
|
||||||
|
// 'target' and 'source' objects. Returns the parsing result. Error message will be set in 'err'
|
||||||
|
// on parsing error, and the contents in 'target' and 'source' will be undefined.
|
||||||
|
static bool ParseTargetInfoAndSourceInfo(const std::vector<std::string>& tokens,
|
||||||
|
const std::string& tgt_hash, TargetInfo* target,
|
||||||
|
const std::string& src_hash, SourceInfo* source,
|
||||||
|
std::string* err);
|
||||||
|
|
||||||
|
// Allows parsing ABORT command, which should be used for testing purpose only.
|
||||||
|
static bool abort_allowed_;
|
||||||
|
|
||||||
|
// The type of the command.
|
||||||
|
Type type_{ Type::LAST };
|
||||||
|
// The index of the Command object, which is specified by the caller.
|
||||||
|
size_t index_{ 0 };
|
||||||
|
// The input string that the Command object is parsed from.
|
||||||
|
std::string cmdline_;
|
||||||
|
// The patch info. Only meaningful for BSDIFF and IMGDIFF commands.
|
||||||
|
PatchInfo patch_;
|
||||||
|
// The target info, where the command should be written to.
|
||||||
|
TargetInfo target_;
|
||||||
|
// The source info to load the source blocks for the command.
|
||||||
|
SourceInfo source_;
|
||||||
|
// The stash info. Only meaningful for STASH and FREE commands. Note that although SourceInfo may
|
||||||
|
// also load data from stash, such info will be owned and managed by SourceInfo (i.e. in source_).
|
||||||
|
StashInfo stash_;
|
||||||
|
// The hash_tree info. Only meaningful for COMPUTE_HASH_TREE.
|
||||||
|
HashTreeInfo hash_tree_info_;
|
||||||
|
// The unit size of each block to be used in this command.
|
||||||
|
size_t block_size_{ 4096 };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const Command& command);
|
||||||
|
|
||||||
|
// TransferList represents the info for a transfer list, which is parsed from input text lines
|
||||||
|
// containing commands to transfer data from one place to another on the target partition.
|
||||||
|
//
|
||||||
|
// The creator of the transfer list will guarantee that no block is read (i.e., used as the source
|
||||||
|
// for a patch or move) after it has been written.
|
||||||
|
//
|
||||||
|
// The creator will guarantee that a given stash is loaded (with a stash command) before it's used
|
||||||
|
// in a move/bsdiff/imgdiff command.
|
||||||
|
//
|
||||||
|
// Within one command the source and target ranges may overlap so in general we need to read the
|
||||||
|
// entire source into memory before writing anything to the target blocks.
|
||||||
|
//
|
||||||
|
// All the patch data is concatenated into one patch_data file in the update package. It must be
|
||||||
|
// stored uncompressed because we memory-map it in directly from the archive. (Since patches are
|
||||||
|
// already compressed, we lose very little by not compressing their concatenation.)
|
||||||
|
//
|
||||||
|
// Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more
|
||||||
|
// additional hashes before the range parameters, which are used to check if the command has
|
||||||
|
// already been completed and verify the integrity of the source data.
|
||||||
|
class TransferList {
|
||||||
|
public:
|
||||||
|
// Number of header lines.
|
||||||
|
static constexpr size_t kTransferListHeaderLines = 4;
|
||||||
|
|
||||||
|
TransferList() = default;
|
||||||
|
|
||||||
|
// Parses the given input string and returns a TransferList object. Sets error message if any.
|
||||||
|
static TransferList Parse(const std::string& transfer_list_str, std::string* err);
|
||||||
|
|
||||||
|
int version() const {
|
||||||
|
return version_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t total_blocks() const {
|
||||||
|
return total_blocks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t stash_max_entries() const {
|
||||||
|
return stash_max_entries_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t stash_max_blocks() const {
|
||||||
|
return stash_max_blocks_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command>& commands() const {
|
||||||
|
return commands_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the TransferList is valid.
|
||||||
|
constexpr explicit operator bool() const {
|
||||||
|
return version_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// BBOTA version.
|
||||||
|
int version_{ 0 };
|
||||||
|
// Total number of blocks to be written in this transfer.
|
||||||
|
size_t total_blocks_;
|
||||||
|
// Maximum number of stashes that exist at the same time.
|
||||||
|
size_t stash_max_entries_;
|
||||||
|
// Maximum number of blocks to be stashed.
|
||||||
|
size_t stash_max_blocks_;
|
||||||
|
// Commands in this transfer.
|
||||||
|
std::vector<Command> commands_;
|
||||||
|
};
|
21
updater/include/private/utils.h
Normal file
21
updater/include/private/utils.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
bool SetUpdatedMarker(const std::string& marker);
|
24
updater/include/updater/blockimg.h
Normal file
24
updater/include/updater/blockimg.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 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 _UPDATER_BLOCKIMG_H_
|
||||||
|
#define _UPDATER_BLOCKIMG_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
void RegisterBlockImageFunctions();
|
||||||
|
|
||||||
|
#endif
|
74
updater/include/updater/build_info.h
Normal file
74
updater/include/updater/build_info.h
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
|
||||||
|
// This class serves as the aggregation of the fake block device information during update
|
||||||
|
// simulation on host. In specific, it has the name of the block device, its mount point, and the
|
||||||
|
// path to the temporary file that fakes this block device.
|
||||||
|
class FakeBlockDevice {
|
||||||
|
public:
|
||||||
|
FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path)
|
||||||
|
: blockdev_name(std::move(block_device)),
|
||||||
|
mount_point(std::move(mount_point)),
|
||||||
|
mounted_file_path(std::move(temp_file_path)) {}
|
||||||
|
|
||||||
|
std::string blockdev_name;
|
||||||
|
std::string mount_point;
|
||||||
|
std::string mounted_file_path; // path to the temp file that mocks the block device
|
||||||
|
};
|
||||||
|
|
||||||
|
// This class stores the information of the source build. For example, it creates and maintains
|
||||||
|
// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can
|
||||||
|
// query the information and run the update on host.
|
||||||
|
class BuildInfo {
|
||||||
|
public:
|
||||||
|
BuildInfo(const std::string_view work_dir, bool keep_images)
|
||||||
|
: work_dir_(work_dir), keep_images_(keep_images) {}
|
||||||
|
// Returns the value of the build properties.
|
||||||
|
std::string GetProperty(const std::string_view key, const std::string_view default_value) const;
|
||||||
|
// Returns the path to the mock block device.
|
||||||
|
std::string FindBlockDeviceName(const std::string_view name) const;
|
||||||
|
// Parses the given target-file, initializes the build properties and extracts the images.
|
||||||
|
bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input);
|
||||||
|
|
||||||
|
std::string GetOemSettings() const {
|
||||||
|
return oem_settings_;
|
||||||
|
}
|
||||||
|
void SetOemSettings(const std::string_view oem_settings) {
|
||||||
|
oem_settings_ = oem_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// A map to store the system properties during simulation.
|
||||||
|
std::map<std::string, std::string, std::less<>> build_props_;
|
||||||
|
// A file that contains the oem properties.
|
||||||
|
std::string oem_settings_;
|
||||||
|
// A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the
|
||||||
|
// temporary file.
|
||||||
|
std::map<std::string, FakeBlockDevice, std::less<>> blockdev_map_;
|
||||||
|
|
||||||
|
std::list<TemporaryFile> temp_files_;
|
||||||
|
std::string work_dir_; // A temporary directory to store the extracted image files
|
||||||
|
bool keep_images_;
|
||||||
|
};
|
19
updater/include/updater/dynamic_partitions.h
Normal file
19
updater/include/updater/dynamic_partitions.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void RegisterDynamicPartitionsFunctions();
|
19
updater/include/updater/install.h
Normal file
19
updater/include/updater/install.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void RegisterInstallFunctions();
|
63
updater/include/updater/simulator_runtime.h
Normal file
63
updater/include/updater/simulator_runtime.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "edify/updater_runtime_interface.h"
|
||||||
|
#include "updater/build_info.h"
|
||||||
|
|
||||||
|
class SimulatorRuntime : public UpdaterRuntimeInterface {
|
||||||
|
public:
|
||||||
|
explicit SimulatorRuntime(BuildInfo* source) : source_(source) {}
|
||||||
|
|
||||||
|
bool IsSimulator() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetProperty(const std::string_view key,
|
||||||
|
const std::string_view default_value) const override;
|
||||||
|
|
||||||
|
int Mount(const std::string_view location, const std::string_view mount_point,
|
||||||
|
const std::string_view fs_type, const std::string_view mount_options) override;
|
||||||
|
bool IsMounted(const std::string_view mount_point) const override;
|
||||||
|
std::pair<bool, int> Unmount(const std::string_view mount_point) override;
|
||||||
|
|
||||||
|
bool ReadFileToString(const std::string_view filename, std::string* content) const override;
|
||||||
|
bool WriteStringToFile(const std::string_view content,
|
||||||
|
const std::string_view filename) const override;
|
||||||
|
|
||||||
|
int WipeBlockDevice(const std::string_view filename, size_t len) const override;
|
||||||
|
int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
|
||||||
|
int Tune2Fs(const std::vector<std::string>& args) const override;
|
||||||
|
|
||||||
|
bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
|
||||||
|
bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
|
||||||
|
bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
|
||||||
|
std::string AddSlotSuffix(const std::string_view arg) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string FindBlockDeviceName(const std::string_view name) const override;
|
||||||
|
|
||||||
|
BuildInfo* source_;
|
||||||
|
std::map<std::string, std::string, std::less<>> mounted_partitions_;
|
||||||
|
};
|
71
updater/include/updater/target_files.h
Normal file
71
updater/include/updater/target_files.h
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <ziparchive/zip_archive.h>
|
||||||
|
|
||||||
|
// This class represents the mount information for each line in a fstab file.
|
||||||
|
class FstabInfo {
|
||||||
|
public:
|
||||||
|
FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type)
|
||||||
|
: blockdev_name(std::move(blockdev_name)),
|
||||||
|
mount_point(std::move(mount_point)),
|
||||||
|
fs_type(std::move(fs_type)) {}
|
||||||
|
|
||||||
|
std::string blockdev_name;
|
||||||
|
std::string mount_point;
|
||||||
|
std::string fs_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This class parses a target file from a zip file or an extracted directory. It also provides the
|
||||||
|
// function to read its content for simulation.
|
||||||
|
class TargetFile {
|
||||||
|
public:
|
||||||
|
TargetFile(std::string path, bool extracted_input)
|
||||||
|
: path_(std::move(path)), extracted_input_(extracted_input) {}
|
||||||
|
|
||||||
|
// Opens the input target file (or extracted directory) and parses the misc_info.txt.
|
||||||
|
bool Open();
|
||||||
|
// Parses the build properties in all possible locations and save them in |props_map|
|
||||||
|
bool GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const;
|
||||||
|
// Parses the fstab and save the information about each partition to mount into |fstab_info_list|.
|
||||||
|
bool ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const;
|
||||||
|
// Returns true if the given entry exists in the target file.
|
||||||
|
bool EntryExists(const std::string_view name) const;
|
||||||
|
// Extracts the image file |entry_name|. Returns true on success.
|
||||||
|
bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
|
||||||
|
const std::string_view work_dir, TemporaryFile* image_file) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Wrapper functions to read the entry from either the zipped target-file, or the extracted input
|
||||||
|
// directory.
|
||||||
|
bool ReadEntryToString(const std::string_view name, std::string* content) const;
|
||||||
|
bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const;
|
||||||
|
|
||||||
|
std::string path_; // Path to the zipped target-file or an extracted directory.
|
||||||
|
bool extracted_input_; // True if the target-file has been extracted.
|
||||||
|
ZipArchiveHandle handle_{ nullptr };
|
||||||
|
|
||||||
|
// The properties under META/misc_info.txt
|
||||||
|
std::map<std::string, std::string, std::less<>> misc_info_;
|
||||||
|
};
|
96
updater/include/updater/updater.h
Normal file
96
updater/include/updater/updater.h
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <ziparchive/zip_archive.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "edify/updater_interface.h"
|
||||||
|
#include "otautil/error_code.h"
|
||||||
|
#include "otautil/sysutil.h"
|
||||||
|
|
||||||
|
class Updater : public UpdaterInterface {
|
||||||
|
public:
|
||||||
|
explicit Updater(std::unique_ptr<UpdaterRuntimeInterface> run_time)
|
||||||
|
: runtime_(std::move(run_time)) {}
|
||||||
|
|
||||||
|
~Updater() override;
|
||||||
|
|
||||||
|
// Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and
|
||||||
|
// UpdaterRuntime.
|
||||||
|
bool Init(int fd, const std::string_view package_filename, bool is_retry);
|
||||||
|
|
||||||
|
// Parses and evaluates the updater-script in the OTA package. Reports the error code if the
|
||||||
|
// evaluation fails.
|
||||||
|
bool RunUpdate();
|
||||||
|
|
||||||
|
// Writes the message to command pipe, adds a new line in the end.
|
||||||
|
void WriteToCommandPipe(const std::string_view message, bool flush = false) const override;
|
||||||
|
|
||||||
|
// Sends over the message to recovery to print it on the screen.
|
||||||
|
void UiPrint(const std::string_view message) const override;
|
||||||
|
|
||||||
|
std::string FindBlockDeviceName(const std::string_view name) const override;
|
||||||
|
|
||||||
|
UpdaterRuntimeInterface* GetRuntime() const override {
|
||||||
|
return runtime_.get();
|
||||||
|
}
|
||||||
|
ZipArchiveHandle GetPackageHandle() const override {
|
||||||
|
return package_handle_;
|
||||||
|
}
|
||||||
|
std::string GetResult() const override {
|
||||||
|
return result_;
|
||||||
|
}
|
||||||
|
uint8_t* GetMappedPackageAddress() const override {
|
||||||
|
return mapped_package_.addr;
|
||||||
|
}
|
||||||
|
size_t GetMappedPackageLength() const override {
|
||||||
|
return mapped_package_.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class UpdaterTestBase;
|
||||||
|
friend class UpdaterTest;
|
||||||
|
// Where in the package we expect to find the edify script to execute.
|
||||||
|
// (Note it's "updateR-script", not the older "update-script".)
|
||||||
|
static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script";
|
||||||
|
|
||||||
|
// Reads the entry |name| in the zip archive and put the result in |content|.
|
||||||
|
bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content);
|
||||||
|
|
||||||
|
// Parses the error code embedded in state->errmsg; and reports the error code and cause code.
|
||||||
|
void ParseAndReportErrorCode(State* state);
|
||||||
|
|
||||||
|
std::unique_ptr<UpdaterRuntimeInterface> runtime_;
|
||||||
|
|
||||||
|
MemMapping mapped_package_;
|
||||||
|
ZipArchiveHandle package_handle_{ nullptr };
|
||||||
|
std::string updater_script_;
|
||||||
|
|
||||||
|
bool is_retry_{ false };
|
||||||
|
std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
|
||||||
|
|
||||||
|
std::string result_;
|
||||||
|
std::vector<std::string> skipped_functions_;
|
||||||
|
};
|
63
updater/include/updater/updater_runtime.h
Normal file
63
updater/include/updater/updater_runtime.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "edify/updater_runtime_interface.h"
|
||||||
|
|
||||||
|
struct selabel_handle;
|
||||||
|
|
||||||
|
class UpdaterRuntime : public UpdaterRuntimeInterface {
|
||||||
|
public:
|
||||||
|
explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
|
||||||
|
~UpdaterRuntime() override = default;
|
||||||
|
|
||||||
|
bool IsSimulator() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetProperty(const std::string_view key,
|
||||||
|
const std::string_view default_value) const override;
|
||||||
|
|
||||||
|
std::string FindBlockDeviceName(const std::string_view name) const override;
|
||||||
|
|
||||||
|
int Mount(const std::string_view location, const std::string_view mount_point,
|
||||||
|
const std::string_view fs_type, const std::string_view mount_options) override;
|
||||||
|
bool IsMounted(const std::string_view mount_point) const override;
|
||||||
|
std::pair<bool, int> Unmount(const std::string_view mount_point) override;
|
||||||
|
|
||||||
|
bool ReadFileToString(const std::string_view filename, std::string* content) const override;
|
||||||
|
bool WriteStringToFile(const std::string_view content,
|
||||||
|
const std::string_view filename) const override;
|
||||||
|
|
||||||
|
int WipeBlockDevice(const std::string_view filename, size_t len) const override;
|
||||||
|
int RunProgram(const std::vector<std::string>& args, bool is_vfork) const override;
|
||||||
|
int Tune2Fs(const std::vector<std::string>& args) const override;
|
||||||
|
|
||||||
|
bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override;
|
||||||
|
bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override;
|
||||||
|
bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
|
||||||
|
std::string AddSlotSuffix(const std::string_view arg) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct selabel_handle* sehandle_{ nullptr };
|
||||||
|
};
|
910
updater/install.cpp
Normal file
910
updater/install.cpp
Normal file
|
@ -0,0 +1,910 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 "updater/install.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <ftw.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/capability.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/xattr.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utime.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parsedouble.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/properties.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <applypatch/applypatch.h>
|
||||||
|
#include <bootloader_message/bootloader_message.h>
|
||||||
|
#include <ext4_utils/wipe.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <selinux/label.h>
|
||||||
|
#include <selinux/selinux.h>
|
||||||
|
#include <ziparchive/zip_archive.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "edify/updater_interface.h"
|
||||||
|
#include "edify/updater_runtime_interface.h"
|
||||||
|
#include "otautil/dirutil.h"
|
||||||
|
#include "otautil/error_code.h"
|
||||||
|
#include "otautil/print_sha1.h"
|
||||||
|
#include "otautil/sysutil.h"
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
#include <cutils/memory.h> // for strlcpy
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) {
|
||||||
|
CHECK(updater);
|
||||||
|
std::string name = updater->FindBlockDeviceName(partition->name);
|
||||||
|
if (name.empty()) {
|
||||||
|
LOG(ERROR) << "Failed to find the block device " << partition->name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
partition->name = std::move(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
|
||||||
|
// the recovery side for on-screen display.
|
||||||
|
Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string buffer = android::base::Join(args, "");
|
||||||
|
state->updater->UiPrint(buffer);
|
||||||
|
return StringValue(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// package_extract_file(package_file[, dest_file])
|
||||||
|
// Extracts a single package_file from the update package and writes it to dest_file,
|
||||||
|
// overwriting existing files if necessary. Without the dest_file argument, returns the
|
||||||
|
// contents of the package file as a binary blob.
|
||||||
|
Value* PackageExtractFileFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() < 1 || argv.size() > 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.size() == 2) {
|
||||||
|
// The two-argument version extracts to a file.
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
const std::string& zip_path = args[0];
|
||||||
|
std::string dest_path = args[1];
|
||||||
|
|
||||||
|
ZipArchiveHandle za = state->updater->GetPackageHandle();
|
||||||
|
ZipEntry64 entry;
|
||||||
|
if (FindEntry(za, zip_path, &entry) != 0) {
|
||||||
|
LOG(ERROR) << name << ": no " << zip_path << " in package";
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the destination of package_extract_file if it's a block device. During simulation the
|
||||||
|
// destination will map to a fake file.
|
||||||
|
if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path);
|
||||||
|
!block_device_name.empty()) {
|
||||||
|
dest_path = block_device_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(
|
||||||
|
open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOG(ERROR) << name << ": can't open " << dest_path << " for write";
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
int32_t ret = ExtractEntryToFile(za, &entry, fd);
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" ("
|
||||||
|
<< entry.uncompressed_length << " bytes) to \"" << dest_path
|
||||||
|
<< "\": " << ErrorCodeString(ret);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
if (fsync(fd) == -1) {
|
||||||
|
PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(fd.release()) != 0) {
|
||||||
|
PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(success ? "t" : "");
|
||||||
|
} else {
|
||||||
|
// The one-argument version returns the contents of the file as the result.
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
const std::string& zip_path = args[0];
|
||||||
|
|
||||||
|
ZipArchiveHandle za = state->updater->GetPackageHandle();
|
||||||
|
ZipEntry64 entry;
|
||||||
|
if (FindEntry(za, zip_path, &entry) != 0) {
|
||||||
|
return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
|
||||||
|
zip_path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string buffer;
|
||||||
|
if (entry.uncompressed_length > std::numeric_limits<size_t>::max()) {
|
||||||
|
return ErrorAbort(state, kPackageExtractFileFailure,
|
||||||
|
"%s(): Entry `%s` Uncompressed size exceeds size of address space.", name,
|
||||||
|
zip_path.c_str());
|
||||||
|
}
|
||||||
|
buffer.resize(entry.uncompressed_length);
|
||||||
|
|
||||||
|
int32_t ret =
|
||||||
|
ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
|
||||||
|
if (ret != 0) {
|
||||||
|
return ErrorAbort(state, kPackageExtractFileFailure,
|
||||||
|
"%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
|
||||||
|
zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Value(Value::Type::BLOB, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch_partition_check(target_partition, source_partition)
|
||||||
|
// Checks if the target and source partitions have the desired checksums to be patched. It returns
|
||||||
|
// directly, if the target partition already has the expected checksum. Otherwise it in turn
|
||||||
|
// checks the integrity of the source partition and the backup file on /cache.
|
||||||
|
//
|
||||||
|
// For example, patch_partition_check(
|
||||||
|
// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d",
|
||||||
|
// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4")
|
||||||
|
Value* PatchPartitionCheckFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure,
|
||||||
|
"%s(): Invalid number of args (expected 2, got %zu)", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args, 0, 2)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
auto target = Partition::Parse(args[0], &err);
|
||||||
|
if (!target) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name,
|
||||||
|
args[0].c_str(), err.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source = Partition::Parse(args[1], &err);
|
||||||
|
if (!source) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name,
|
||||||
|
args[1].c_str(), err.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
|
||||||
|
!UpdateBlockDeviceNameForPartition(state->updater, &target)) {
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = PatchPartitionCheck(target, source);
|
||||||
|
return StringValue(result ? "t" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch_partition(target, source, patch)
|
||||||
|
// Applies the given patch to the source partition, and writes the result to the target partition.
|
||||||
|
//
|
||||||
|
// For example, patch_partition(
|
||||||
|
// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d",
|
||||||
|
// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4",
|
||||||
|
// package_extract_file("boot.img.p"))
|
||||||
|
Value* PatchPartitionFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 3) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure,
|
||||||
|
"%s(): Invalid number of args (expected 3, got %zu)", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args, 0, 2)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
auto target = Partition::Parse(args[0], &err);
|
||||||
|
if (!target) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name,
|
||||||
|
args[0].c_str(), err.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source = Partition::Parse(args[1], &err);
|
||||||
|
if (!source) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name,
|
||||||
|
args[1].c_str(), err.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Value>> values;
|
||||||
|
if (!ReadValueArgs(state, argv, &values, 2, 1) || values[0]->type != Value::Type::BLOB) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UpdateBlockDeviceNameForPartition(state->updater, &source) ||
|
||||||
|
!UpdateBlockDeviceNameForPartition(state->updater, &target)) {
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = PatchPartition(target, source, *values[0], nullptr, true);
|
||||||
|
return StringValue(result ? "t" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount(fs_type, partition_type, location, mount_point)
|
||||||
|
// mount(fs_type, partition_type, location, mount_point, mount_options)
|
||||||
|
|
||||||
|
// fs_type="ext4" partition_type="EMMC" location=device
|
||||||
|
Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 4 && argv.size() != 5) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& fs_type = args[0];
|
||||||
|
const std::string& partition_type = args[1];
|
||||||
|
const std::string& location = args[2];
|
||||||
|
const std::string& mount_point = args[3];
|
||||||
|
std::string mount_options;
|
||||||
|
|
||||||
|
if (argv.size() == 5) {
|
||||||
|
mount_options = args[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs_type.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name);
|
||||||
|
}
|
||||||
|
if (partition_type.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty",
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
if (location.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name);
|
||||||
|
}
|
||||||
|
if (mount_point.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty",
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater = state->updater;
|
||||||
|
if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) {
|
||||||
|
updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name,
|
||||||
|
location.c_str(), mount_point.c_str(),
|
||||||
|
strerror(errno)));
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(mount_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_mounted(mount_point)
|
||||||
|
Value* IsMountedFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& mount_point = args[0];
|
||||||
|
if (mount_point.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure,
|
||||||
|
"mount_point argument to unmount() can't be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (!updater_runtime->IsMounted(mount_point)) {
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(mount_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& mount_point = args[0];
|
||||||
|
if (mount_point.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure,
|
||||||
|
"mount_point argument to unmount() can't be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater = state->updater;
|
||||||
|
auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point);
|
||||||
|
if (!mounted) {
|
||||||
|
updater->UiPrint(
|
||||||
|
android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str()));
|
||||||
|
return nullptr;
|
||||||
|
} else if (result != 0) {
|
||||||
|
updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(),
|
||||||
|
strerror(errno)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(mount_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
// format(fs_type, partition_type, location, fs_size, mount_point)
|
||||||
|
//
|
||||||
|
// fs_type="ext4" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location>
|
||||||
|
// fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location>
|
||||||
|
// if fs_size == 0, then make fs uses the entire partition.
|
||||||
|
// if fs_size > 0, that is the size to use
|
||||||
|
// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs")
|
||||||
|
Value* FormatFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 5) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& fs_type = args[0];
|
||||||
|
const std::string& partition_type = args[1];
|
||||||
|
const std::string& location = args[2];
|
||||||
|
const std::string& fs_size = args[3];
|
||||||
|
const std::string& mount_point = args[4];
|
||||||
|
|
||||||
|
if (fs_type.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name);
|
||||||
|
}
|
||||||
|
if (partition_type.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty",
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
if (location.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name);
|
||||||
|
}
|
||||||
|
if (mount_point.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty",
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t size;
|
||||||
|
if (!android::base::ParseInt(fs_size, &size)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name,
|
||||||
|
fs_size.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (fs_type == "ext4") {
|
||||||
|
std::vector<std::string> mke2fs_args = {
|
||||||
|
"/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location
|
||||||
|
};
|
||||||
|
if (size != 0) {
|
||||||
|
mke2fs_args.push_back(std::to_string(size / 4096LL));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) {
|
||||||
|
LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = updater_runtime->RunProgram(
|
||||||
|
{ "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true);
|
||||||
|
status != 0) {
|
||||||
|
LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
return StringValue(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs_type == "f2fs") {
|
||||||
|
if (size < 0) {
|
||||||
|
LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
std::vector<std::string> f2fs_args = {
|
||||||
|
"/system/bin/make_f2fs", "-g", "android", "-w", "512", location
|
||||||
|
};
|
||||||
|
if (size >= 512) {
|
||||||
|
f2fs_args.push_back(std::to_string(size / 512));
|
||||||
|
}
|
||||||
|
if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) {
|
||||||
|
LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = updater_runtime->RunProgram(
|
||||||
|
{ "/system/bin/sload_f2fs", "-t", mount_point, location }, true);
|
||||||
|
status != 0) {
|
||||||
|
LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \""
|
||||||
|
<< partition_type << "\"";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* ShowProgressFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& frac_str = args[0];
|
||||||
|
const std::string& sec_str = args[1];
|
||||||
|
|
||||||
|
double frac;
|
||||||
|
if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name,
|
||||||
|
frac_str.c_str());
|
||||||
|
}
|
||||||
|
int sec;
|
||||||
|
if (!android::base::ParseInt(sec_str.c_str(), &sec)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name,
|
||||||
|
sec_str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec));
|
||||||
|
|
||||||
|
return StringValue(frac_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* SetProgressFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& frac_str = args[0];
|
||||||
|
|
||||||
|
double frac;
|
||||||
|
if (!android::base::ParseDouble(frac_str.c_str(), &frac)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name,
|
||||||
|
frac_str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac));
|
||||||
|
|
||||||
|
return StringValue(frac_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
std::string key;
|
||||||
|
if (!Evaluate(state, argv[0], &key)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
std::string value = updater_runtime->GetProperty(key, "");
|
||||||
|
|
||||||
|
return StringValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// file_getprop(file, key)
|
||||||
|
//
|
||||||
|
// interprets 'file' as a getprop-style file (key=value pairs, one
|
||||||
|
// per line. # comment lines, blank lines, lines without '=' ignored),
|
||||||
|
// and returns the value for 'key' (or "" if it isn't defined).
|
||||||
|
Value* FileGetPropFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& filename = args[0];
|
||||||
|
const std::string& key = args[1];
|
||||||
|
|
||||||
|
std::string buffer;
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (!updater_runtime->ReadFileToString(filename, &buffer)) {
|
||||||
|
ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> lines = android::base::Split(buffer, "\n");
|
||||||
|
for (size_t i = 0; i < lines.size(); i++) {
|
||||||
|
std::string line = android::base::Trim(lines[i]);
|
||||||
|
|
||||||
|
// comment or blank line: skip to next line
|
||||||
|
if (line.empty() || line[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
size_t equal_pos = line.find('=');
|
||||||
|
if (equal_pos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim whitespace between key and '='
|
||||||
|
std::string str = android::base::Trim(line.substr(0, equal_pos));
|
||||||
|
|
||||||
|
// not the key we're looking for
|
||||||
|
if (key != str) continue;
|
||||||
|
|
||||||
|
return StringValue(android::base::Trim(line.substr(equal_pos + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply_patch_space(bytes)
|
||||||
|
Value* ApplyPatchSpaceFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& bytes_str = args[0];
|
||||||
|
|
||||||
|
size_t bytes;
|
||||||
|
if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name,
|
||||||
|
bytes_str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the cache size check if the update is a retry.
|
||||||
|
if (state->is_retry || CheckAndFreeSpaceOnCache(bytes)) {
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* WipeCacheFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (!argv.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
state->updater->WriteToCommandPipe("wipe_cache");
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* RunProgramFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() < 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
auto status = updater_runtime->RunProgram(args, false);
|
||||||
|
return StringValue(std::to_string(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
// read_file(filename)
|
||||||
|
// Reads a local file 'filename' and returns its contents as a string Value.
|
||||||
|
Value* ReadFileFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& filename = args[0];
|
||||||
|
|
||||||
|
std::string contents;
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (updater_runtime->ReadFileToString(filename, &contents)) {
|
||||||
|
return new Value(Value::Type::STRING, std::move(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave it to caller to handle the failure.
|
||||||
|
PLOG(ERROR) << name << ": Failed to read " << filename;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// write_value(value, filename)
|
||||||
|
// Writes 'value' to 'filename'.
|
||||||
|
// Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq")
|
||||||
|
Value* WriteValueFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& filename = args[1];
|
||||||
|
if (filename.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Filename cannot be empty", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& value = args[0];
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (!updater_runtime->WriteStringToFile(value, filename)) {
|
||||||
|
PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\"";
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately reboot the device. Recovery is not finished normally,
|
||||||
|
// so if you reboot into recovery it will re-start applying the
|
||||||
|
// current package (because nothing has cleared the copy of the
|
||||||
|
// arguments stored in the BCB).
|
||||||
|
//
|
||||||
|
// The argument is the partition name passed to the android reboot
|
||||||
|
// property. It can be "recovery" to boot from the recovery
|
||||||
|
// partition, or "" (empty string) to boot from the regular boot
|
||||||
|
// partition.
|
||||||
|
Value* RebootNowFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& filename = args[0];
|
||||||
|
const std::string& property = args[1];
|
||||||
|
|
||||||
|
// Zero out the 'command' field of the bootloader message. Leave the rest intact.
|
||||||
|
bootloader_message boot;
|
||||||
|
std::string err;
|
||||||
|
if (!read_bootloader_message_from(&boot, filename, &err)) {
|
||||||
|
LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
memset(boot.command, 0, sizeof(boot.command));
|
||||||
|
if (!write_bootloader_message_to(boot, filename, &err)) {
|
||||||
|
LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
Reboot(property);
|
||||||
|
|
||||||
|
return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a string value somewhere that future invocations of recovery
|
||||||
|
// can access it. This value is called the "stage" and can be used to
|
||||||
|
// drive packages that need to do reboots in the middle of
|
||||||
|
// installation and keep track of where they are in the multi-stage
|
||||||
|
// install.
|
||||||
|
//
|
||||||
|
// The first argument is the block device for the misc partition
|
||||||
|
// ("/misc" in the fstab), which is where this value is stored. The
|
||||||
|
// second argument is the string to store; it should not exceed 31
|
||||||
|
// bytes.
|
||||||
|
Value* SetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& filename = args[0];
|
||||||
|
const std::string& stagestr = args[1];
|
||||||
|
|
||||||
|
// Store this value in the misc partition, immediately after the
|
||||||
|
// bootloader message that the main recovery uses to save its
|
||||||
|
// arguments in case of the device restarting midway through
|
||||||
|
// package installation.
|
||||||
|
bootloader_message boot;
|
||||||
|
std::string err;
|
||||||
|
if (!read_bootloader_message_from(&boot, filename, &err)) {
|
||||||
|
LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
strlcpy(boot.stage, stagestr.c_str(), sizeof(boot.stage));
|
||||||
|
if (!write_bootloader_message_to(boot, filename, &err)) {
|
||||||
|
LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the value most recently saved with SetStageFn. The argument
|
||||||
|
// is the block device for the misc partition.
|
||||||
|
Value* GetStageFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& filename = args[0];
|
||||||
|
|
||||||
|
bootloader_message boot;
|
||||||
|
std::string err;
|
||||||
|
if (!read_bootloader_message_from(&boot, filename, &err)) {
|
||||||
|
LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err;
|
||||||
|
return StringValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringValue(boot.stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 2) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& filename = args[0];
|
||||||
|
const std::string& len_str = args[1];
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
if (!android::base::ParseUint(len_str.c_str(), &len)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
int status = updater_runtime->WipeBlockDevice(filename, len);
|
||||||
|
return StringValue(status == 0 ? "t" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* EnableRebootFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (!argv.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name,
|
||||||
|
argv.size());
|
||||||
|
}
|
||||||
|
state->updater->WriteToCommandPipe("enable_reboot");
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.empty()) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tune2fs expects the program name as its first arg.
|
||||||
|
args.insert(args.begin(), "tune2fs");
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
if (auto result = updater_runtime->Tune2Fs(args); result != 0) {
|
||||||
|
return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
|
||||||
|
}
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* AddSlotSuffixFn(const char* name, State* state,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& argv) {
|
||||||
|
if (argv.size() != 1) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
|
||||||
|
}
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!ReadArgs(state, argv, &args)) {
|
||||||
|
return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
|
||||||
|
}
|
||||||
|
const std::string& arg = args[0];
|
||||||
|
auto updater_runtime = state->updater->GetRuntime();
|
||||||
|
return StringValue(updater_runtime->AddSlotSuffix(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterInstallFunctions() {
|
||||||
|
RegisterFunction("mount", MountFn);
|
||||||
|
RegisterFunction("is_mounted", IsMountedFn);
|
||||||
|
RegisterFunction("unmount", UnmountFn);
|
||||||
|
RegisterFunction("format", FormatFn);
|
||||||
|
RegisterFunction("show_progress", ShowProgressFn);
|
||||||
|
RegisterFunction("set_progress", SetProgressFn);
|
||||||
|
RegisterFunction("package_extract_file", PackageExtractFileFn);
|
||||||
|
|
||||||
|
RegisterFunction("getprop", GetPropFn);
|
||||||
|
RegisterFunction("file_getprop", FileGetPropFn);
|
||||||
|
|
||||||
|
RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
|
||||||
|
RegisterFunction("patch_partition", PatchPartitionFn);
|
||||||
|
RegisterFunction("patch_partition_check", PatchPartitionCheckFn);
|
||||||
|
|
||||||
|
RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
|
||||||
|
|
||||||
|
RegisterFunction("read_file", ReadFileFn);
|
||||||
|
RegisterFunction("write_value", WriteValueFn);
|
||||||
|
|
||||||
|
RegisterFunction("wipe_cache", WipeCacheFn);
|
||||||
|
|
||||||
|
RegisterFunction("ui_print", UIPrintFn);
|
||||||
|
|
||||||
|
RegisterFunction("run_program", RunProgramFn);
|
||||||
|
|
||||||
|
RegisterFunction("reboot_now", RebootNowFn);
|
||||||
|
RegisterFunction("get_stage", GetStageFn);
|
||||||
|
RegisterFunction("set_stage", SetStageFn);
|
||||||
|
|
||||||
|
RegisterFunction("enable_reboot", EnableRebootFn);
|
||||||
|
RegisterFunction("tune2fs", Tune2FsFn);
|
||||||
|
|
||||||
|
RegisterFunction("add_slot_suffix", AddSlotSuffixFn);
|
||||||
|
}
|
82
updater/mounts.cpp
Normal file
82
updater/mounts.cpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2007 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 "mounts.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <mntent.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
|
||||||
|
struct MountedVolume {
|
||||||
|
std::string device;
|
||||||
|
std::string mount_point;
|
||||||
|
std::string filesystem;
|
||||||
|
std::string flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<MountedVolume*> g_mounts_state;
|
||||||
|
|
||||||
|
bool scan_mounted_volumes() {
|
||||||
|
for (size_t i = 0; i < g_mounts_state.size(); ++i) {
|
||||||
|
delete g_mounts_state[i];
|
||||||
|
}
|
||||||
|
g_mounts_state.clear();
|
||||||
|
|
||||||
|
// Open and read mount table entries.
|
||||||
|
FILE* fp = setmntent("/proc/mounts", "re");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mntent* e;
|
||||||
|
while ((e = getmntent(fp)) != NULL) {
|
||||||
|
MountedVolume* v = new MountedVolume;
|
||||||
|
v->device = e->mnt_fsname;
|
||||||
|
v->mount_point = e->mnt_dir;
|
||||||
|
v->filesystem = e->mnt_type;
|
||||||
|
v->flags = e->mnt_opts;
|
||||||
|
g_mounts_state.push_back(v);
|
||||||
|
}
|
||||||
|
endmntent(fp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) {
|
||||||
|
for (size_t i = 0; i < g_mounts_state.size(); ++i) {
|
||||||
|
if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int unmount_mounted_volume(MountedVolume* volume) {
|
||||||
|
// Intentionally pass the empty string to umount if the caller tries to unmount a volume they
|
||||||
|
// already unmounted using this function.
|
||||||
|
std::string mount_point = volume->mount_point;
|
||||||
|
volume->mount_point.clear();
|
||||||
|
int result = umount(mount_point.c_str());
|
||||||
|
if (result == -1) {
|
||||||
|
PLOG(WARNING) << "Failed to umount " << mount_point;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
25
updater/mounts.h
Normal file
25
updater/mounts.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2007 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct MountedVolume;
|
||||||
|
|
||||||
|
bool scan_mounted_volumes();
|
||||||
|
|
||||||
|
MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point);
|
||||||
|
|
||||||
|
int unmount_mounted_volume(MountedVolume* volume);
|
137
updater/simulator_runtime.cpp
Normal file
137
updater/simulator_runtime.cpp
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "updater/simulator_runtime.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/properties.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <ext4_utils/wipe.h>
|
||||||
|
#include <selinux/label.h>
|
||||||
|
|
||||||
|
#include "mounts.h"
|
||||||
|
#include "otautil/sysutil.h"
|
||||||
|
|
||||||
|
std::string SimulatorRuntime::GetProperty(const std::string_view key,
|
||||||
|
const std::string_view default_value) const {
|
||||||
|
return source_->GetProperty(key, default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point,
|
||||||
|
const std::string_view /* fs_type */,
|
||||||
|
const std::string_view /* mount_options */) {
|
||||||
|
if (auto mounted_location = mounted_partitions_.find(mount_point);
|
||||||
|
mounted_location != mounted_partitions_.end() && mounted_location->second != location) {
|
||||||
|
LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted_partitions_.emplace(mount_point, location);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const {
|
||||||
|
return mounted_partitions_.find(mount_point) != mounted_partitions_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, int> SimulatorRuntime::Unmount(const std::string_view mount_point) {
|
||||||
|
if (!IsMounted(mount_point)) {
|
||||||
|
return { false, -1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted_partitions_.erase(std::string(mount_point));
|
||||||
|
return { true, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const {
|
||||||
|
return source_->FindBlockDeviceName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(xunchang) implement the utility functions in simulator.
|
||||||
|
int SimulatorRuntime::RunProgram(const std::vector<std::string>& args, bool /* is_vfork */) const {
|
||||||
|
LOG(INFO) << "Running program with args " << android::base::Join(args, " ");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& args) const {
|
||||||
|
LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " ");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const {
|
||||||
|
LOG(INFO) << "SKip wiping block device " << filename;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SimulatorRuntime::ReadFileToString(const std::string_view filename,
|
||||||
|
std::string* content) const {
|
||||||
|
if (android::base::EndsWith(filename, "oem.prop")) {
|
||||||
|
return android::base::ReadFileToString(source_->GetOemSettings(), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << "SKip reading filename " << filename;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SimulatorRuntime::WriteStringToFile(const std::string_view content,
|
||||||
|
const std::string_view filename) const {
|
||||||
|
LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
|
||||||
|
std::string* path) {
|
||||||
|
*path = partition_name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
|
||||||
|
LOG(INFO) << "Skip unmapping " << partition_name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
|
||||||
|
const std::unordered_set<std::string> commands{
|
||||||
|
"resize", "remove", "add", "move",
|
||||||
|
"add_group", "resize_group", "remove_group", "remove_all_groups",
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
auto tokens = android::base::Split(line, " ");
|
||||||
|
if (commands.find(tokens[0]) == commands.end()) {
|
||||||
|
LOG(ERROR) << "Unknown operation in op_list: " << line;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SimulatorRuntime::AddSlotSuffix(const std::string_view arg) const {
|
||||||
|
LOG(INFO) << "Skip adding slot suffix to " << arg;
|
||||||
|
return std::string(arg);
|
||||||
|
}
|
294
updater/target_files.cpp
Normal file
294
updater/target_files.cpp
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "updater/target_files.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <sparse/sparse.h>
|
||||||
|
|
||||||
|
static bool SimgToImg(int input_fd, int output_fd) {
|
||||||
|
if (lseek64(input_fd, 0, SEEK_SET) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to lseek64 on the input sparse image";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lseek64(output_fd, 0, SEEK_SET) == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to lseek64 on the output raw image";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)> s_file(
|
||||||
|
sparse_file_import(input_fd, true, false), sparse_file_destroy);
|
||||||
|
if (!s_file) {
|
||||||
|
LOG(ERROR) << "Failed to import the sparse image.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) {
|
||||||
|
PLOG(ERROR) << "Failed to output the raw image file.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParsePropertyFile(const std::string_view prop_content,
|
||||||
|
std::map<std::string, std::string, std::less<>>* props_map) {
|
||||||
|
LOG(INFO) << "Start parsing build property\n";
|
||||||
|
std::vector<std::string> lines = android::base::Split(std::string(prop_content), "\n");
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
auto pos = line.find('=');
|
||||||
|
if (pos == std::string::npos) continue;
|
||||||
|
std::string key = line.substr(0, pos);
|
||||||
|
std::string value = line.substr(pos + 1);
|
||||||
|
LOG(INFO) << key << ": " << value;
|
||||||
|
props_map->emplace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParseFstab(const std::string_view fstab, std::vector<FstabInfo>* fstab_info_list) {
|
||||||
|
LOG(INFO) << "parsing fstab\n";
|
||||||
|
std::vector<std::string> lines = android::base::Split(std::string(fstab), "\n");
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
|
||||||
|
// <block_device> <mount_point> <fs_type> <mount_flags> optional:<fs_mgr_flags>
|
||||||
|
std::vector<std::string> tokens = android::base::Split(line, " ");
|
||||||
|
tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end());
|
||||||
|
if (tokens.size() != 4 && tokens.size() != 5) {
|
||||||
|
LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl
|
||||||
|
<< "Error parsing fstab line: " << line;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& blockdev = tokens[0];
|
||||||
|
const auto& mount_point = tokens[1];
|
||||||
|
const auto& fs_type = tokens[2];
|
||||||
|
if (!android::base::StartsWith(mount_point, "/")) {
|
||||||
|
LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The simulator only supports ext4 and emmc for now.
|
||||||
|
if (fs_type != "ext4" && fs_type != "emmc") {
|
||||||
|
LOG(WARNING) << "Unsupported fs_type in " << line;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fstab_info_list->emplace_back(blockdev, mount_point, fs_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::EntryExists(const std::string_view name) const {
|
||||||
|
if (extracted_input_) {
|
||||||
|
std::string entry_path = path_ + "/" + std::string(name);
|
||||||
|
if (access(entry_path.c_str(), O_RDONLY) != 0) {
|
||||||
|
PLOG(WARNING) << "Failed to access " << entry_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(handle_);
|
||||||
|
ZipEntry64 img_entry;
|
||||||
|
return FindEntry(handle_, name, &img_entry) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const {
|
||||||
|
if (extracted_input_) {
|
||||||
|
std::string entry_path = path_ + "/" + std::string(name);
|
||||||
|
return android::base::ReadFileToString(entry_path, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(handle_);
|
||||||
|
ZipEntry64 entry;
|
||||||
|
if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
|
||||||
|
LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.uncompressed_length == 0) {
|
||||||
|
content->clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.uncompressed_length > std::numeric_limits<size_t>::max()) {
|
||||||
|
LOG(ERROR) << "Failed to extract " << name
|
||||||
|
<< " because's uncompressed size exceeds size of address space. "
|
||||||
|
<< entry.uncompressed_length;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
content->resize(entry.uncompressed_length);
|
||||||
|
if (auto extract_err = ExtractToMemory(
|
||||||
|
handle_, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), entry.uncompressed_length);
|
||||||
|
extract_err != 0) {
|
||||||
|
LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::ExtractEntryToTempFile(const std::string_view name,
|
||||||
|
TemporaryFile* temp_file) const {
|
||||||
|
if (extracted_input_) {
|
||||||
|
std::string entry_path = path_ + "/" + std::string(name);
|
||||||
|
return std::filesystem::copy_file(entry_path, temp_file->path,
|
||||||
|
std::filesystem::copy_options::overwrite_existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(handle_);
|
||||||
|
ZipEntry64 entry;
|
||||||
|
if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) {
|
||||||
|
LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) {
|
||||||
|
LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::Open() {
|
||||||
|
if (!extracted_input_) {
|
||||||
|
if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) {
|
||||||
|
LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the misc info.
|
||||||
|
std::string misc_info_content;
|
||||||
|
if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ParsePropertyFile(misc_info_content, &misc_info_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::GetBuildProps(std::map<std::string, std::string, std::less<>>* props_map) const {
|
||||||
|
props_map->clear();
|
||||||
|
// Parse the source zip to mock the system props and block devices. We try all the possible
|
||||||
|
// locations for build props.
|
||||||
|
constexpr std::string_view kPropLocations[] = {
|
||||||
|
"SYSTEM/build.prop",
|
||||||
|
"VENDOR/build.prop",
|
||||||
|
"PRODUCT/build.prop",
|
||||||
|
"SYSTEM_EXT/build.prop",
|
||||||
|
"SYSTEM/vendor/build.prop",
|
||||||
|
"SYSTEM/product/build.prop",
|
||||||
|
"SYSTEM/system_ext/build.prop",
|
||||||
|
"ODM/build.prop", // legacy
|
||||||
|
"ODM/etc/build.prop",
|
||||||
|
"VENDOR/odm/build.prop", // legacy
|
||||||
|
"VENDOR/odm/etc/build.prop",
|
||||||
|
};
|
||||||
|
for (const auto& name : kPropLocations) {
|
||||||
|
std::string build_prop_content;
|
||||||
|
if (!ReadEntryToString(name, &build_prop_content)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::map<std::string, std::string, std::less<>> props;
|
||||||
|
if (!ParsePropertyFile(build_prop_content, &props)) {
|
||||||
|
LOG(ERROR) << "Failed to parse build prop in " << name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : props) {
|
||||||
|
if (auto it = props_map->find(key); it != props_map->end() && it->second != value) {
|
||||||
|
LOG(WARNING) << "Property " << key << " has different values in property files, we got "
|
||||||
|
<< it->second << " and " << value;
|
||||||
|
}
|
||||||
|
props_map->emplace(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info,
|
||||||
|
const std::string_view work_dir, TemporaryFile* image_file) const {
|
||||||
|
if (!EntryExists(entry_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need extra work for 'emmc'; use the image file as the block device.
|
||||||
|
if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) {
|
||||||
|
if (!ExtractEntryToTempFile(entry_name, image_file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else { // treated as ext4 sparse image
|
||||||
|
TemporaryFile sparse_image{ std::string(work_dir) };
|
||||||
|
if (!ExtractEntryToTempFile(entry_name, &sparse_image)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the sparse image to raw.
|
||||||
|
if (!SimgToImg(sparse_image.fd, image_file->fd)) {
|
||||||
|
LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetFile::ParseFstabInfo(std::vector<FstabInfo>* fstab_info_list) const {
|
||||||
|
// Parse the fstab file and extract the image files. The location of the fstab actually depends
|
||||||
|
// on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities.
|
||||||
|
constexpr std::string_view kRecoveryFstabLocations[] = {
|
||||||
|
"RECOVERY/RAMDISK/system/etc/recovery.fstab",
|
||||||
|
"RECOVERY/RAMDISK/etc/recovery.fstab",
|
||||||
|
"BOOT/RAMDISK/system/etc/recovery.fstab",
|
||||||
|
"BOOT/RAMDISK/etc/recovery.fstab",
|
||||||
|
};
|
||||||
|
std::string fstab_content;
|
||||||
|
for (const auto& name : kRecoveryFstabLocations) {
|
||||||
|
if (std::string content; ReadEntryToString(name, &content)) {
|
||||||
|
fstab_content = std::move(content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fstab_content.empty()) {
|
||||||
|
LOG(ERROR) << "Failed to parse the recovery fstab file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the images and convert them to raw.
|
||||||
|
if (!ParseFstab(fstab_content, fstab_info_list)) {
|
||||||
|
LOG(ERROR) << "Failed to mount the block devices for source build.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
167
updater/update_simulator_main.cpp
Normal file
167
updater/update_simulator_main.cpp
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 <getopt.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "otautil/error_code.h"
|
||||||
|
#include "otautil/paths.h"
|
||||||
|
#include "updater/blockimg.h"
|
||||||
|
#include "updater/build_info.h"
|
||||||
|
#include "updater/dynamic_partitions.h"
|
||||||
|
#include "updater/install.h"
|
||||||
|
#include "updater/simulator_runtime.h"
|
||||||
|
#include "updater/updater.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
void Usage(std::string_view name) {
|
||||||
|
LOG(INFO) << "Usage: " << name << "[--oem_settings <oem_property_file>]"
|
||||||
|
<< "[--skip_functions <skip_function_file>]"
|
||||||
|
<< " --source <source_target_file>"
|
||||||
|
<< " --ota_package <ota_package>";
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* SimulatorPlaceHolderFn(const char* name, State* /* state */,
|
||||||
|
const std::vector<std::unique_ptr<Expr>>& /* argv */) {
|
||||||
|
LOG(INFO) << "Skip function " << name << " in host simulation";
|
||||||
|
return StringValue("t");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
// Write the logs to stdout.
|
||||||
|
android::base::InitLogging(argv, &android::base::StderrLogger);
|
||||||
|
|
||||||
|
std::string oem_settings;
|
||||||
|
std::string skip_function_file;
|
||||||
|
std::string source_target_file;
|
||||||
|
std::string package_name;
|
||||||
|
std::string work_dir;
|
||||||
|
bool keep_images = false;
|
||||||
|
|
||||||
|
constexpr struct option OPTIONS[] = {
|
||||||
|
{ "keep_images", no_argument, nullptr, 0 },
|
||||||
|
{ "oem_settings", required_argument, nullptr, 0 },
|
||||||
|
{ "ota_package", required_argument, nullptr, 0 },
|
||||||
|
{ "skip_functions", required_argument, nullptr, 0 },
|
||||||
|
{ "source", required_argument, nullptr, 0 },
|
||||||
|
{ "work_dir", required_argument, nullptr, 0 },
|
||||||
|
{ nullptr, 0, nullptr, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
int arg;
|
||||||
|
int option_index;
|
||||||
|
while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
|
||||||
|
if (arg != 0) {
|
||||||
|
LOG(ERROR) << "Invalid command argument";
|
||||||
|
Usage(argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
auto option_name = OPTIONS[option_index].name;
|
||||||
|
// The same oem property file used during OTA generation. It's needed for file_getprop() to
|
||||||
|
// return the correct value for the source build.
|
||||||
|
if (option_name == "oem_settings"s) {
|
||||||
|
oem_settings = optarg;
|
||||||
|
} else if (option_name == "skip_functions"s) {
|
||||||
|
skip_function_file = optarg;
|
||||||
|
} else if (option_name == "source"s) {
|
||||||
|
source_target_file = optarg;
|
||||||
|
} else if (option_name == "ota_package"s) {
|
||||||
|
package_name = optarg;
|
||||||
|
} else if (option_name == "keep_images"s) {
|
||||||
|
keep_images = true;
|
||||||
|
} else if (option_name == "work_dir"s) {
|
||||||
|
work_dir = optarg;
|
||||||
|
} else {
|
||||||
|
Usage(argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_target_file.empty() || package_name.empty()) {
|
||||||
|
Usage(argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure edify's functions.
|
||||||
|
RegisterBuiltins();
|
||||||
|
RegisterInstallFunctions();
|
||||||
|
RegisterBlockImageFunctions();
|
||||||
|
RegisterDynamicPartitionsFunctions();
|
||||||
|
|
||||||
|
if (!skip_function_file.empty()) {
|
||||||
|
std::string content;
|
||||||
|
if (!android::base::ReadFileToString(skip_function_file, &content)) {
|
||||||
|
PLOG(ERROR) << "Failed to read " << skip_function_file;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lines = android::base::Split(content, "\n");
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (line.empty() || android::base::StartsWith(line, "#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RegisterFunction(line, SimulatorPlaceHolderFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TemporaryFile temp_saved_source;
|
||||||
|
TemporaryFile temp_last_command;
|
||||||
|
TemporaryDir temp_stash_base;
|
||||||
|
|
||||||
|
Paths::Get().set_cache_temp_source(temp_saved_source.path);
|
||||||
|
Paths::Get().set_last_command_file(temp_last_command.path);
|
||||||
|
Paths::Get().set_stash_directory_base(temp_stash_base.path);
|
||||||
|
|
||||||
|
TemporaryFile cmd_pipe;
|
||||||
|
TemporaryDir source_temp_dir;
|
||||||
|
if (work_dir.empty()) {
|
||||||
|
work_dir = source_temp_dir.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildInfo source_build_info(work_dir, keep_images);
|
||||||
|
if (!source_build_info.ParseTargetFile(source_target_file, false)) {
|
||||||
|
LOG(ERROR) << "Failed to parse the target file " << source_target_file;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oem_settings.empty()) {
|
||||||
|
CHECK_EQ(0, access(oem_settings.c_str(), R_OK));
|
||||||
|
source_build_info.SetOemSettings(oem_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater updater(std::make_unique<SimulatorRuntime>(&source_build_info));
|
||||||
|
if (!updater.Init(cmd_pipe.release(), package_name, false)) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updater.RunUpdate()) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
189
updater/updater.cpp
Normal file
189
updater/updater.cpp
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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 "updater/updater.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
|
||||||
|
#include "edify/updater_runtime_interface.h"
|
||||||
|
|
||||||
|
Updater::~Updater() {
|
||||||
|
if (package_handle_) {
|
||||||
|
CloseArchive(package_handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) {
|
||||||
|
// Set up the pipe for sending commands back to the parent process.
|
||||||
|
cmd_pipe_.reset(fdopen(fd, "wb"));
|
||||||
|
if (!cmd_pipe_) {
|
||||||
|
LOG(ERROR) << "Failed to open the command pipe";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setlinebuf(cmd_pipe_.get());
|
||||||
|
|
||||||
|
if (!mapped_package_.MapFile(std::string(package_filename))) {
|
||||||
|
LOG(ERROR) << "failed to map package " << package_filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length,
|
||||||
|
std::string(package_filename).c_str(), &package_handle_);
|
||||||
|
open_err != 0) {
|
||||||
|
LOG(ERROR) << "failed to open package " << package_filename << ": "
|
||||||
|
<< ErrorCodeString(open_err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_retry_ = is_retry;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::RunUpdate() {
|
||||||
|
CHECK(runtime_);
|
||||||
|
|
||||||
|
// Parse the script.
|
||||||
|
std::unique_ptr<Expr> root;
|
||||||
|
int error_count = 0;
|
||||||
|
int error = ParseString(updater_script_, &root, &error_count);
|
||||||
|
if (error != 0 || error_count > 0) {
|
||||||
|
LOG(ERROR) << error_count << " parse errors";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the parsed script.
|
||||||
|
State state(updater_script_, this);
|
||||||
|
state.is_retry = is_retry_;
|
||||||
|
|
||||||
|
bool status = Evaluate(&state, root, &result_);
|
||||||
|
if (status) {
|
||||||
|
fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str());
|
||||||
|
// Even though the script doesn't abort, still log the cause code if result is empty.
|
||||||
|
if (result_.empty() && state.cause_code != kNoCause) {
|
||||||
|
fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code);
|
||||||
|
}
|
||||||
|
for (const auto& func : skipped_functions_) {
|
||||||
|
LOG(WARNING) << "Skipped executing function " << func;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseAndReportErrorCode(&state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const {
|
||||||
|
fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str());
|
||||||
|
if (flush) {
|
||||||
|
fflush(cmd_pipe_.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::UiPrint(const std::string_view message) const {
|
||||||
|
// "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
|
||||||
|
// so skip sending empty strings to ui.
|
||||||
|
std::vector<std::string> lines = android::base::Split(std::string(message), "\n");
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
if (!line.empty()) {
|
||||||
|
fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on the updater side, we need to dump the contents to stderr (which has
|
||||||
|
// been redirected to the log file). because the recovery will only print
|
||||||
|
// the contents to screen when processing pipe command ui_print.
|
||||||
|
LOG(INFO) << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Updater::FindBlockDeviceName(const std::string_view name) const {
|
||||||
|
return runtime_->FindBlockDeviceName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::ParseAndReportErrorCode(State* state) {
|
||||||
|
CHECK(state);
|
||||||
|
if (state->errmsg.empty()) {
|
||||||
|
LOG(ERROR) << "script aborted (no error message)";
|
||||||
|
fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n");
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "script aborted: " << state->errmsg;
|
||||||
|
const std::vector<std::string> lines = android::base::Split(state->errmsg, "\n");
|
||||||
|
for (const std::string& line : lines) {
|
||||||
|
// Parse the error code in abort message.
|
||||||
|
// Example: "E30: This package is for bullhead devices."
|
||||||
|
if (!line.empty() && line[0] == 'E') {
|
||||||
|
if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) {
|
||||||
|
LOG(ERROR) << "Failed to parse error code: [" << line << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installation has been aborted. Set the error code to kScriptExecutionFailure unless
|
||||||
|
// a more specific code has been set in errmsg.
|
||||||
|
if (state->error_code == kNoError) {
|
||||||
|
state->error_code = kScriptExecutionFailure;
|
||||||
|
}
|
||||||
|
fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code);
|
||||||
|
// Cause code should provide additional information about the abort.
|
||||||
|
if (state->cause_code != kNoCause) {
|
||||||
|
fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code);
|
||||||
|
if (state->cause_code == kPatchApplicationFailure) {
|
||||||
|
LOG(INFO) << "Patch application failed, retry update.";
|
||||||
|
fprintf(cmd_pipe_.get(), "retry_update\n");
|
||||||
|
} else if (state->cause_code == kEioFailure) {
|
||||||
|
LOG(INFO) << "Update failed due to EIO, retry update.";
|
||||||
|
fprintf(cmd_pipe_.get(), "retry_update\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name,
|
||||||
|
std::string* content) {
|
||||||
|
ZipEntry64 entry;
|
||||||
|
int find_err = FindEntry(za, entry_name, &entry);
|
||||||
|
if (find_err != 0) {
|
||||||
|
LOG(ERROR) << "failed to find " << entry_name
|
||||||
|
<< " in the package: " << ErrorCodeString(find_err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (entry.uncompressed_length > std::numeric_limits<size_t>::max()) {
|
||||||
|
LOG(ERROR) << "Failed to extract " << entry_name
|
||||||
|
<< " because's uncompressed size exceeds size of address space. "
|
||||||
|
<< entry.uncompressed_length;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
content->resize(entry.uncompressed_length);
|
||||||
|
int extract_err = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&content->at(0)),
|
||||||
|
entry.uncompressed_length);
|
||||||
|
if (extract_err != 0) {
|
||||||
|
LOG(ERROR) << "failed to read " << entry_name
|
||||||
|
<< " from package: " << ErrorCodeString(extract_err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
116
updater/updater_main.cpp
Normal file
116
updater/updater_main.cpp
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <openssl/crypto.h>
|
||||||
|
#include <selinux/android.h>
|
||||||
|
#include <selinux/label.h>
|
||||||
|
#include <selinux/selinux.h>
|
||||||
|
|
||||||
|
#include "edify/expr.h"
|
||||||
|
#include "updater/blockimg.h"
|
||||||
|
#include "updater/dynamic_partitions.h"
|
||||||
|
#include "updater/install.h"
|
||||||
|
#include "updater/updater.h"
|
||||||
|
#include "updater/updater_runtime.h"
|
||||||
|
|
||||||
|
// Generated by the makefile, this function defines the
|
||||||
|
// RegisterDeviceExtensions() function, which calls all the
|
||||||
|
// registration functions for device-specific extensions.
|
||||||
|
#include "register.inc"
|
||||||
|
|
||||||
|
static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */,
|
||||||
|
const char* /* tag */, const char* /* file */, unsigned int /* line */,
|
||||||
|
const char* message) {
|
||||||
|
fprintf(stdout, "%s\n", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
// Various things log information to stdout or stderr more or less
|
||||||
|
// at random (though we've tried to standardize on stdout). The
|
||||||
|
// log file makes more sense if buffering is turned off so things
|
||||||
|
// appear in the right order.
|
||||||
|
setbuf(stdout, nullptr);
|
||||||
|
setbuf(stderr, nullptr);
|
||||||
|
|
||||||
|
// We don't have logcat yet under recovery. Update logs will always be written to stdout
|
||||||
|
// (which is redirected to recovery.log).
|
||||||
|
android::base::InitLogging(argv, &UpdaterLogger);
|
||||||
|
|
||||||
|
// Run the libcrypto KAT(known answer tests) based self tests.
|
||||||
|
if (BORINGSSL_self_test() != 1) {
|
||||||
|
LOG(ERROR) << "Failed to run the boringssl self tests";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc != 4 && argc != 5) {
|
||||||
|
LOG(ERROR) << "unexpected number of arguments: " << argc;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* version = argv[1];
|
||||||
|
if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
|
||||||
|
// We support version 1, 2, or 3.
|
||||||
|
LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
if (!android::base::ParseInt(argv[2], &fd)) {
|
||||||
|
LOG(ERROR) << "Failed to parse fd in " << argv[2];
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string package_name = argv[3];
|
||||||
|
|
||||||
|
bool is_retry = false;
|
||||||
|
if (argc == 5) {
|
||||||
|
if (strcmp(argv[4], "retry") == 0) {
|
||||||
|
is_retry = true;
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "unexpected argument: " << argv[4];
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure edify's functions.
|
||||||
|
RegisterBuiltins();
|
||||||
|
RegisterInstallFunctions();
|
||||||
|
RegisterBlockImageFunctions();
|
||||||
|
RegisterDynamicPartitionsFunctions();
|
||||||
|
RegisterDeviceExtensions();
|
||||||
|
|
||||||
|
auto sehandle = selinux_android_file_context_handle();
|
||||||
|
selinux_android_set_sehandle(sehandle);
|
||||||
|
|
||||||
|
Updater updater(std::make_unique<UpdaterRuntime>(sehandle));
|
||||||
|
if (!updater.Init(fd, package_name, is_retry)) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updater.RunUpdate()) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
189
updater/updater_runtime.cpp
Normal file
189
updater/updater_runtime.cpp
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "updater/updater_runtime.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/properties.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <ext4_utils/wipe.h>
|
||||||
|
#include <fs_mgr.h>
|
||||||
|
#include <selinux/label.h>
|
||||||
|
#include <tune2fs.h>
|
||||||
|
|
||||||
|
#include "mounts.h"
|
||||||
|
#include "otautil/sysutil.h"
|
||||||
|
|
||||||
|
std::string UpdaterRuntime::GetProperty(const std::string_view key,
|
||||||
|
const std::string_view default_value) const {
|
||||||
|
return android::base::GetProperty(std::string(key), std::string(default_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const {
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setMountFlag(const std::string& flag, unsigned* mount_flags) {
|
||||||
|
static constexpr std::pair<const char*, unsigned> mount_flags_list[] = {
|
||||||
|
{ "noatime", MS_NOATIME },
|
||||||
|
{ "noexec", MS_NOEXEC },
|
||||||
|
{ "nosuid", MS_NOSUID },
|
||||||
|
{ "nodev", MS_NODEV },
|
||||||
|
{ "nodiratime", MS_NODIRATIME },
|
||||||
|
{ "ro", MS_RDONLY },
|
||||||
|
{ "rw", 0 },
|
||||||
|
{ "remount", MS_REMOUNT },
|
||||||
|
{ "bind", MS_BIND },
|
||||||
|
{ "rec", MS_REC },
|
||||||
|
{ "unbindable", MS_UNBINDABLE },
|
||||||
|
{ "private", MS_PRIVATE },
|
||||||
|
{ "slave", MS_SLAVE },
|
||||||
|
{ "shared", MS_SHARED },
|
||||||
|
{ "defaults", 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [name, value] : mount_flags_list) {
|
||||||
|
if (flag == name) {
|
||||||
|
*mount_flags |= value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseMountFlags(const std::string& flags, unsigned* mount_flags,
|
||||||
|
std::string* fs_options) {
|
||||||
|
bool is_flag_set = false;
|
||||||
|
std::vector<std::string> flag_list;
|
||||||
|
for (const auto& flag : android::base::Split(flags, ",")) {
|
||||||
|
if (!setMountFlag(flag, mount_flags)) {
|
||||||
|
// Unknown flag, so it must be a filesystem specific option.
|
||||||
|
flag_list.push_back(flag);
|
||||||
|
} else {
|
||||||
|
is_flag_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*fs_options = android::base::Join(flag_list, ',');
|
||||||
|
return is_flag_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point,
|
||||||
|
const std::string_view fs_type, const std::string_view mount_options) {
|
||||||
|
std::string mount_point_string(mount_point);
|
||||||
|
std::string mount_options_string(mount_options);
|
||||||
|
char* secontext = nullptr;
|
||||||
|
unsigned mount_flags = 0;
|
||||||
|
std::string fs_options;
|
||||||
|
|
||||||
|
if (sehandle_) {
|
||||||
|
selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755);
|
||||||
|
setfscreatecon(secontext);
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir(mount_point_string.c_str(), 0755);
|
||||||
|
|
||||||
|
if (secontext) {
|
||||||
|
freecon(secontext);
|
||||||
|
setfscreatecon(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parseMountFlags(mount_options_string, &mount_flags, &fs_options)) {
|
||||||
|
// Fall back to default
|
||||||
|
mount_flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mount(std::string(location).c_str(), mount_point_string.c_str(),
|
||||||
|
std::string(fs_type).c_str(), mount_flags, fs_options.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const {
|
||||||
|
scan_mounted_volumes();
|
||||||
|
MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
|
||||||
|
return vol != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, int> UpdaterRuntime::Unmount(const std::string_view mount_point) {
|
||||||
|
scan_mounted_volumes();
|
||||||
|
MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str());
|
||||||
|
if (vol == nullptr) {
|
||||||
|
return { false, -1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = unmount_mounted_volume(vol);
|
||||||
|
return { true, ret };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const {
|
||||||
|
return android::base::ReadFileToString(std::string(filename), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterRuntime::WriteStringToFile(const std::string_view content,
|
||||||
|
const std::string_view filename) const {
|
||||||
|
return android::base::WriteStringToFile(std::string(content), std::string(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const {
|
||||||
|
android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY));
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOG(ERROR) << "Failed to open " << filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure.
|
||||||
|
return wipe_block_device(fd, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int UpdaterRuntime::RunProgram(const std::vector<std::string>& args, bool is_vfork) const {
|
||||||
|
CHECK(!args.empty());
|
||||||
|
auto argv = StringVectorToNullTerminatedArray(args);
|
||||||
|
LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args";
|
||||||
|
|
||||||
|
pid_t child = is_vfork ? vfork() : fork();
|
||||||
|
if (child == 0) {
|
||||||
|
execv(argv[0], argv.data());
|
||||||
|
PLOG(ERROR) << "run_program: execv failed";
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int status;
|
||||||
|
waitpid(child, &status, 0);
|
||||||
|
if (WIFEXITED(status)) {
|
||||||
|
if (WEXITSTATUS(status) != 0) {
|
||||||
|
LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status);
|
||||||
|
}
|
||||||
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UpdaterRuntime::Tune2Fs(const std::vector<std::string>& args) const {
|
||||||
|
auto tune2fs_args = StringVectorToNullTerminatedArray(args);
|
||||||
|
// tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success.
|
||||||
|
return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UpdaterRuntime::AddSlotSuffix(const std::string_view arg) const {
|
||||||
|
return std::string(arg) + fs_mgr_get_slot_suffix();
|
||||||
|
}
|
356
updater/updater_runtime_dynamic_partitions.cpp
Normal file
356
updater/updater_runtime_dynamic_partitions.cpp
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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 "updater/updater_runtime.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iterator>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/parseint.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
|
#include <fs_mgr.h>
|
||||||
|
#include <fs_mgr_dm_linear.h>
|
||||||
|
#include <libdm/dm.h>
|
||||||
|
#include <liblp/builder.h>
|
||||||
|
|
||||||
|
using android::dm::DeviceMapper;
|
||||||
|
using android::dm::DmDeviceState;
|
||||||
|
using android::fs_mgr::CreateLogicalPartition;
|
||||||
|
using android::fs_mgr::CreateLogicalPartitionParams;
|
||||||
|
using android::fs_mgr::DestroyLogicalPartition;
|
||||||
|
using android::fs_mgr::LpMetadata;
|
||||||
|
using android::fs_mgr::MetadataBuilder;
|
||||||
|
using android::fs_mgr::Partition;
|
||||||
|
using android::fs_mgr::PartitionOpener;
|
||||||
|
using android::fs_mgr::SlotNumberForSlotSuffix;
|
||||||
|
|
||||||
|
static constexpr std::chrono::milliseconds kMapTimeout{ 1000 };
|
||||||
|
|
||||||
|
static std::string GetSuperDevice() {
|
||||||
|
return "/dev/block/by-name/" + fs_mgr_get_super_partition_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string AddSlotSuffix(const std::string& partition_name) {
|
||||||
|
return partition_name + fs_mgr_get_slot_suffix();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool UnmapPartitionWithSuffixOnDeviceMapper(const std::string& partition_name_suffix) {
|
||||||
|
auto state = DeviceMapper::Instance().GetState(partition_name_suffix);
|
||||||
|
if (state == DmDeviceState::INVALID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (state == DmDeviceState::ACTIVE) {
|
||||||
|
return DestroyLogicalPartition(partition_name_suffix);
|
||||||
|
}
|
||||||
|
LOG(ERROR) << "Unknown device mapper state: "
|
||||||
|
<< static_cast<std::underlying_type_t<DmDeviceState>>(state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name,
|
||||||
|
std::string* path) {
|
||||||
|
auto partition_name_suffix = AddSlotSuffix(partition_name);
|
||||||
|
auto state = DeviceMapper::Instance().GetState(partition_name_suffix);
|
||||||
|
if (state == DmDeviceState::INVALID) {
|
||||||
|
CreateLogicalPartitionParams params = {
|
||||||
|
.block_device = GetSuperDevice(),
|
||||||
|
// If device supports A/B, apply non-A/B update to the partition at current slot. Otherwise,
|
||||||
|
// SlotNumberForSlotSuffix("") returns 0.
|
||||||
|
.metadata_slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()),
|
||||||
|
// If device supports A/B, apply non-A/B update to the partition at current slot. Otherwise,
|
||||||
|
// fs_mgr_get_slot_suffix() returns empty string.
|
||||||
|
.partition_name = partition_name_suffix,
|
||||||
|
.force_writable = true,
|
||||||
|
.timeout_ms = kMapTimeout,
|
||||||
|
};
|
||||||
|
return CreateLogicalPartition(params, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == DmDeviceState::ACTIVE) {
|
||||||
|
return DeviceMapper::Instance().GetDmDevicePathByName(partition_name_suffix, path);
|
||||||
|
}
|
||||||
|
LOG(ERROR) << "Unknown device mapper state: "
|
||||||
|
<< static_cast<std::underlying_type_t<DmDeviceState>>(state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
|
||||||
|
return ::UnmapPartitionWithSuffixOnDeviceMapper(AddSlotSuffix(partition_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace { // Ops
|
||||||
|
|
||||||
|
struct OpParameters {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
MetadataBuilder* builder;
|
||||||
|
|
||||||
|
bool ExpectArgSize(size_t size) const {
|
||||||
|
CHECK(!tokens.empty());
|
||||||
|
auto actual = tokens.size() - 1;
|
||||||
|
if (actual != size) {
|
||||||
|
LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const std::string& op() const {
|
||||||
|
CHECK(!tokens.empty());
|
||||||
|
return tokens[0];
|
||||||
|
}
|
||||||
|
const std::string& arg(size_t pos) const {
|
||||||
|
CHECK_LE(pos + 1, tokens.size());
|
||||||
|
return tokens[pos + 1];
|
||||||
|
}
|
||||||
|
std::optional<uint64_t> uint_arg(size_t pos, const std::string& name) const {
|
||||||
|
auto str = arg(pos);
|
||||||
|
uint64_t ret;
|
||||||
|
if (!android::base::ParseUint(str, &ret)) {
|
||||||
|
LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using OpFunction = std::function<bool(const OpParameters&)>;
|
||||||
|
using OpMap = std::map<std::string, OpFunction>;
|
||||||
|
|
||||||
|
bool PerformOpResize(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(2)) return false;
|
||||||
|
const auto& partition_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
auto size = params.uint_arg(1, "size");
|
||||||
|
if (!size.has_value()) return false;
|
||||||
|
|
||||||
|
auto partition = params.builder->FindPartition(partition_name_suffix);
|
||||||
|
if (partition == nullptr) {
|
||||||
|
LOG(ERROR) << "Failed to find partition " << partition_name_suffix
|
||||||
|
<< " in dynamic partition metadata.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) {
|
||||||
|
LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before resizing.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!params.builder->ResizePartition(partition, size.value())) {
|
||||||
|
LOG(ERROR) << "Failed to resize partition " << partition_name_suffix << " to size " << *size
|
||||||
|
<< ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpRemove(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(1)) return false;
|
||||||
|
const auto& partition_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
|
||||||
|
if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) {
|
||||||
|
LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before removing.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
params.builder->RemovePartition(partition_name_suffix);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpAdd(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(2)) return false;
|
||||||
|
const auto& partition_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
const auto& group_name_suffix = AddSlotSuffix(params.arg(1));
|
||||||
|
|
||||||
|
if (params.builder->AddPartition(partition_name_suffix, group_name_suffix,
|
||||||
|
LP_PARTITION_ATTR_READONLY) == nullptr) {
|
||||||
|
LOG(ERROR) << "Failed to add partition " << partition_name_suffix << " to group "
|
||||||
|
<< group_name_suffix << ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpMove(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(2)) return false;
|
||||||
|
const auto& partition_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
const auto& new_group_name_suffix = AddSlotSuffix(params.arg(1));
|
||||||
|
|
||||||
|
auto partition = params.builder->FindPartition(partition_name_suffix);
|
||||||
|
if (partition == nullptr) {
|
||||||
|
LOG(ERROR) << "Cannot move partition " << partition_name_suffix << " to group "
|
||||||
|
<< new_group_name_suffix << " because it is not found.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto old_group_name_suffix = partition->group_name();
|
||||||
|
if (old_group_name_suffix != new_group_name_suffix) {
|
||||||
|
if (!params.builder->ChangePartitionGroup(partition, new_group_name_suffix)) {
|
||||||
|
LOG(ERROR) << "Cannot move partition " << partition_name_suffix << " from group "
|
||||||
|
<< old_group_name_suffix << " to group " << new_group_name_suffix << ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpAddGroup(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(2)) return false;
|
||||||
|
const auto& group_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
auto maximum_size = params.uint_arg(1, "maximum_size");
|
||||||
|
if (!maximum_size.has_value()) return false;
|
||||||
|
|
||||||
|
auto group = params.builder->FindGroup(group_name_suffix);
|
||||||
|
if (group != nullptr) {
|
||||||
|
LOG(ERROR) << "Cannot add group " << group_name_suffix << " because it already exists.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maximum_size.value() == 0) {
|
||||||
|
LOG(WARNING) << "Adding group " << group_name_suffix << " with no size limits.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.builder->AddGroup(group_name_suffix, maximum_size.value())) {
|
||||||
|
LOG(ERROR) << "Failed to add group " << group_name_suffix << " with maximum size "
|
||||||
|
<< maximum_size.value() << ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpResizeGroup(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(2)) return false;
|
||||||
|
const auto& group_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
auto new_size = params.uint_arg(1, "maximum_size");
|
||||||
|
if (!new_size.has_value()) return false;
|
||||||
|
|
||||||
|
auto group = params.builder->FindGroup(group_name_suffix);
|
||||||
|
if (group == nullptr) {
|
||||||
|
LOG(ERROR) << "Cannot resize group " << group_name_suffix << " because it is not found.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto old_size = group->maximum_size();
|
||||||
|
if (old_size != new_size.value()) {
|
||||||
|
if (!params.builder->ChangeGroupSize(group_name_suffix, new_size.value())) {
|
||||||
|
LOG(ERROR) << "Cannot resize group " << group_name_suffix << " from " << old_size << " to "
|
||||||
|
<< new_size.value() << ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
|
||||||
|
const std::string& group_name_suffix) {
|
||||||
|
auto partitions = builder->ListPartitionsInGroup(group_name_suffix);
|
||||||
|
std::vector<std::string> partition_names;
|
||||||
|
std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names),
|
||||||
|
[](Partition* partition) { return partition->name(); });
|
||||||
|
return partition_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpRemoveGroup(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(1)) return false;
|
||||||
|
const auto& group_name_suffix = AddSlotSuffix(params.arg(0));
|
||||||
|
|
||||||
|
auto partition_names = ListPartitionNamesInGroup(params.builder, group_name_suffix);
|
||||||
|
if (!partition_names.empty()) {
|
||||||
|
LOG(ERROR) << "Cannot remove group " << group_name_suffix
|
||||||
|
<< " because it still contains partitions ["
|
||||||
|
<< android::base::Join(partition_names, ", ") << "]";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
params.builder->RemoveGroupAndPartitions(group_name_suffix);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerformOpRemoveAllGroups(const OpParameters& params) {
|
||||||
|
if (!params.ExpectArgSize(0)) return false;
|
||||||
|
|
||||||
|
auto group_names = params.builder->ListGroups();
|
||||||
|
for (const auto& group_name_suffix : group_names) {
|
||||||
|
auto partition_names = ListPartitionNamesInGroup(params.builder, group_name_suffix);
|
||||||
|
for (const auto& partition_name_suffix : partition_names) {
|
||||||
|
if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) {
|
||||||
|
LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before removing group "
|
||||||
|
<< group_name_suffix << ".";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.builder->RemoveGroupAndPartitions(group_name_suffix);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) {
|
||||||
|
auto super_device = GetSuperDevice();
|
||||||
|
auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0);
|
||||||
|
if (builder == nullptr) {
|
||||||
|
LOG(ERROR) << "Failed to load dynamic partition metadata.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const OpMap op_map{
|
||||||
|
// clang-format off
|
||||||
|
{"resize", PerformOpResize},
|
||||||
|
{"remove", PerformOpRemove},
|
||||||
|
{"add", PerformOpAdd},
|
||||||
|
{"move", PerformOpMove},
|
||||||
|
{"add_group", PerformOpAddGroup},
|
||||||
|
{"resize_group", PerformOpResizeGroup},
|
||||||
|
{"remove_group", PerformOpRemoveGroup},
|
||||||
|
{"remove_all_groups", PerformOpRemoveAllGroups},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> lines = android::base::Split(std::string(op_list_value), "\n");
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
auto comment_idx = line.find('#');
|
||||||
|
auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx);
|
||||||
|
op_and_args = android::base::Trim(op_and_args);
|
||||||
|
if (op_and_args.empty()) continue;
|
||||||
|
|
||||||
|
auto tokens = android::base::Split(op_and_args, " ");
|
||||||
|
const auto& op = tokens[0];
|
||||||
|
auto it = op_map.find(op);
|
||||||
|
if (it == op_map.end()) {
|
||||||
|
LOG(ERROR) << "Unknown operation in op_list: " << op;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OpParameters params;
|
||||||
|
params.tokens = tokens;
|
||||||
|
params.builder = builder.get();
|
||||||
|
if (!it->second(params)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto metadata = builder->Export();
|
||||||
|
if (metadata == nullptr) {
|
||||||
|
LOG(ERROR) << "Failed to export metadata.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UpdatePartitionTable(super_device, *metadata, 0)) {
|
||||||
|
LOG(ERROR) << "Failed to write metadata.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
Loading…
Reference in a new issue