Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4aa60b38 authored by Tom Cherry's avatar Tom Cherry
Browse files

fastboot: clean up CheckRequirements

CheckRequirements() had various issues that are cleaned up here,

1) Move from C string parsing to C++
2) Moved from C data structures to C++, including fixing memory leaks.
3) Removed the 'cur_product' global and the 'query_save' function that
   stores it
4) Actually writing tests for the parsing function for
android-info.txt
5) Check that a variable needs to be checked for a given product before
   trying to read it.  Previously, fastboot would fail if a variable
   isn't recognized on a device, even if the check should be ignored.

A lot of flexibility is allowed for the input strings, to keep
backwards compatibility with the previous parsers.

Test: fastboot works, unit tests

Change-Id: Idc3bba8b8fe829d8eefe5f6c495e63a9441c0b60
parent dfd85df1
Loading
Loading
Loading
Loading
+0 −75
Original line number Original line Diff line number Diff line
@@ -136,69 +136,6 @@ void fb_resize_partition(const std::string& partition, const std::string& size)
    RUN_COMMAND(fb->RawCommand(FB_CMD_RESIZE_PARTITION ":" + partition + ":" + size));
    RUN_COMMAND(fb->RawCommand(FB_CMD_RESIZE_PARTITION ":" + partition + ":" + size));
}
}


static int match(const char* str, const char** value, unsigned count) {
    unsigned n;

    for (n = 0; n < count; n++) {
        const char *val = value[n];
        int len = strlen(val);
        int match;

        if ((len > 1) && (val[len-1] == '*')) {
            len--;
            match = !strncmp(val, str, len);
        } else {
            match = !strcmp(val, str);
        }

        if (match) return 1;
    }

    return 0;
}

void fb_require(const std::string& product, const std::string& var, bool invert, size_t count,
                const char** values) {
    Status("Checking '" + var + "'");

    double start = now();

    std::string var_value;
    auto status = fb->GetVar(var, &var_value);

    if (status) {
        fprintf(stderr, "getvar:%s FAILED (%s)\n", var.c_str(), fb->Error().c_str());
        die("requirements not met!");
    }

    if (!product.empty()) {
        if (product != cur_product) {
            double split = now();
            fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n", cur_product,
                    product.c_str(), (split - start));
            return;
        }
    }

    int yes = match(var_value.c_str(), values, count);
    if (invert) yes = !yes;

    if (yes) {
        double split = now();
        fprintf(stderr, "OKAY [%7.3fs]\n", (split - start));
        return;
    }

    fprintf(stderr, "FAILED\n\n");
    fprintf(stderr, "Device %s is '%s'.\n", var.c_str(), var_value.c_str());
    fprintf(stderr, "Update %s '%s'", invert ? "rejects" : "requires", values[0]);
    for (size_t n = 1; n < count; n++) {
        fprintf(stderr, " or '%s'", values[n]);
    }
    fprintf(stderr, ".\n\n");
    die("requirements not met!");
}

void fb_display(const std::string& label, const std::string& var) {
void fb_display(const std::string& label, const std::string& var) {
    std::string value;
    std::string value;
    auto status = fb->GetVar(var, &value);
    auto status = fb->GetVar(var, &value);
@@ -210,18 +147,6 @@ void fb_display(const std::string& label, const std::string& var) {
    fprintf(stderr, "%s: %s\n", label.c_str(), value.c_str());
    fprintf(stderr, "%s: %s\n", label.c_str(), value.c_str());
}
}


void fb_query_save(const std::string& var, char* dest, uint32_t dest_size) {
    std::string value;
    auto status = fb->GetVar(var, &value);

    if (status) {
        fprintf(stderr, "getvar:%s FAILED (%s)\n", var.c_str(), fb->Error().c_str());
        return;
    }

    strncpy(dest, value.c_str(), dest_size);
}

void fb_reboot() {
void fb_reboot() {
    fprintf(stderr, "Rebooting");
    fprintf(stderr, "Rebooting");
    fb->Reboot();
    fb->Reboot();
+0 −3
Original line number Original line Diff line number Diff line
@@ -53,10 +53,7 @@ void fb_flash_fd(const std::string& partition, int fd, uint32_t sz);
void fb_flash_sparse(const std::string& partition, struct sparse_file* s, uint32_t sz,
void fb_flash_sparse(const std::string& partition, struct sparse_file* s, uint32_t sz,
                     size_t current, size_t total);
                     size_t current, size_t total);
void fb_erase(const std::string& partition);
void fb_erase(const std::string& partition);
void fb_require(const std::string& prod, const std::string& var, bool invert, size_t nvalues,
                const char** values);
void fb_display(const std::string& label, const std::string& var);
void fb_display(const std::string& label, const std::string& var);
void fb_query_save(const std::string& var, char* dest, uint32_t dest_size);
void fb_reboot();
void fb_reboot();
void fb_command(const std::string& cmd, const std::string& msg);
void fb_command(const std::string& cmd, const std::string& msg);
void fb_download(const std::string& name, const std::vector<char>& data);
void fb_download(const std::string& name, const std::vector<char>& data);
+124 −91
Original line number Original line Diff line number Diff line
@@ -43,6 +43,7 @@


#include <chrono>
#include <chrono>
#include <functional>
#include <functional>
#include <regex>
#include <thread>
#include <thread>
#include <utility>
#include <utility>
#include <vector>
#include <vector>
@@ -70,14 +71,14 @@
#include "usb.h"
#include "usb.h"


using android::base::ReadFully;
using android::base::ReadFully;
using android::base::Split;
using android::base::Trim;
using android::base::unique_fd;
using android::base::unique_fd;


#ifndef O_BINARY
#ifndef O_BINARY
#define O_BINARY 0
#define O_BINARY 0
#endif
#endif


char cur_product[FB_RESPONSE_SZ + 1];

static const char* serial = nullptr;
static const char* serial = nullptr;


static bool g_long_listing = false;
static bool g_long_listing = false;
@@ -588,109 +589,145 @@ static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
    return fd.release();
    return fd.release();
}
}


static char* strip(char* s) {
static void CheckRequirement(const std::string& cur_product, const std::string& var,
    while (*s && isspace(*s)) s++;
                             const std::string& product, bool invert,
                             const std::vector<std::string>& options) {
    Status("Checking '" + var + "'");

    double start = now();


    int n = strlen(s);
    if (!product.empty()) {
    while (n-- > 0) {
        if (product != cur_product) {
        if (!isspace(s[n])) break;
            double split = now();
        s[n] = 0;
            fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n",
                    cur_product.c_str(), product.c_str(), (split - start));
            return;
        }
    }
    }
    return s;

    std::string var_value;
    if (!fb_getvar(var, &var_value)) {
        fprintf(stderr, "FAILED\n\n");
        fprintf(stderr, "Could not getvar for '%s' (%s)\n\n", var.c_str(), fb_get_error().c_str());
        die("requirements not met!");
    }
    }


#define MAX_OPTIONS 32
    bool match = false;
static void check_requirement(char* line) {
    for (const auto& option : options) {
    char *val[MAX_OPTIONS];
        if (option == var_value || (option.back() == '*' &&
    unsigned count;
                                    !var_value.compare(0, option.length() - 1, option, 0,
    char *x;
                                                       option.length() - 1))) {
    int invert = 0;
            match = true;
            break;
        }
    }


    if (invert) {
        match = !match;
    }

    if (match) {
        double split = now();
        fprintf(stderr, "OKAY [%7.3fs]\n", (split - start));
        return;
    }

    fprintf(stderr, "FAILED\n\n");
    fprintf(stderr, "Device %s is '%s'.\n", var.c_str(), var_value.c_str());
    fprintf(stderr, "Update %s '%s'", invert ? "rejects" : "requires", options[0].c_str());
    for (auto it = std::next(options.begin()); it != options.end(); ++it) {
        fprintf(stderr, " or '%s'", it->c_str());
    }
    fprintf(stderr, ".\n\n");
    die("requirements not met!");
}

bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product,
                          bool* invert, std::vector<std::string>* options) {
    // "require product=alpha|beta|gamma"
    // "require product=alpha|beta|gamma"
    // "require version-bootloader=1234"
    // "require version-bootloader=1234"
    // "require-for-product:gamma version-bootloader=istanbul|constantinople"
    // "require-for-product:gamma version-bootloader=istanbul|constantinople"
    // "require partition-exists=vendor"
    // "require partition-exists=vendor"
    *product = "";
    *invert = false;

    auto require_reject_regex = std::regex{"(require\\s+|reject\\s+)?\\s*(\\S+)\\s*=\\s*(.*)"};
    auto require_product_regex =
            std::regex{"require-for-product:\\s*(\\S+)\\s+(\\S+)\\s*=\\s*(.*)"};
    std::smatch match_results;

    if (std::regex_match(line, match_results, require_reject_regex)) {
        *invert = Trim(match_results[1]) == "reject";
    } else if (std::regex_match(line, match_results, require_product_regex)) {
        *product = match_results[1];
    } else {
        return false;
    }


    char* name = line;
    *name = match_results[2];
    const char* product = "";
    // Work around an unfortunate name mismatch.
    if (!strncmp(name, "reject ", 7)) {
    if (*name == "board") {
        name += 7;
        *name = "product";
        invert = 1;
    }
    } else if (!strncmp(name, "require ", 8)) {

        name += 8;
    auto raw_options = Split(match_results[3], "|");
        invert = 0;
    for (const auto& option : raw_options) {
    } else if (!strncmp(name, "require-for-product:", 20)) {
        auto trimmed_option = Trim(option);
        // Get the product and point name past it
        options->emplace_back(trimmed_option);
        product = name + 20;
    }
        name = strchr(name, ' ');

        if (!name) die("android-info.txt syntax error: %s", line);
    return true;
        *name = 0;
}
        name += 1;
        invert = 0;
    }

    x = strchr(name, '=');
    if (x == 0) return;
    *x = 0;
    val[0] = x + 1;

    name = strip(name);


// "require partition-exists=x" is a special case, added because of the trouble we had when
// "require partition-exists=x" is a special case, added because of the trouble we had when
// Pixel 2 shipped with new partitions and users used old versions of fastboot to flash them,
// Pixel 2 shipped with new partitions and users used old versions of fastboot to flash them,
// missing out new partitions. A device with new partitions can use "partition-exists" to
// missing out new partitions. A device with new partitions can use "partition-exists" to
// override the fields `optional_if_no_image` in the `images` array.
// override the fields `optional_if_no_image` in the `images` array.
    if (!strcmp(name, "partition-exists")) {
static void HandlePartitionExists(const std::vector<std::string>& options) {
        const char* partition_name = val[0];
    const std::string& partition_name = options[0];
    std::string has_slot;
    std::string has_slot;
        if (!fb_getvar(std::string("has-slot:") + partition_name, &has_slot) ||
    if (!fb_getvar("has-slot:" + partition_name, &has_slot) ||
        (has_slot != "yes" && has_slot != "no")) {
        (has_slot != "yes" && has_slot != "no")) {
            die("device doesn't have required partition %s!", partition_name);
        die("device doesn't have required partition %s!", partition_name.c_str());
    }
    }
    bool known_partition = false;
    bool known_partition = false;
    for (size_t i = 0; i < arraysize(images); ++i) {
    for (size_t i = 0; i < arraysize(images); ++i) {
            if (images[i].nickname && !strcmp(images[i].nickname, partition_name)) {
        if (images[i].nickname && images[i].nickname == partition_name) {
            images[i].optional_if_no_image = false;
            images[i].optional_if_no_image = false;
            known_partition = true;
            known_partition = true;
        }
        }
    }
    }
    if (!known_partition) {
    if (!known_partition) {
        die("device requires partition %s which is not known to this version of fastboot",
        die("device requires partition %s which is not known to this version of fastboot",
                partition_name);
            partition_name.c_str());
    }
    }
        return;
}
}


    for(count = 1; count < MAX_OPTIONS; count++) {
static void CheckRequirements(const std::string& data) {
        x = strchr(val[count - 1],'|');
    std::string cur_product;
        if (x == 0) break;
    if (!fb_getvar("product", &cur_product)) {
        *x = 0;
        fprintf(stderr, "getvar:product FAILED (%s)\n", fb_get_error().c_str());
        val[count] = x + 1;
    }
    }


    // Work around an unfortunate name mismatch.
    auto lines = Split(data, "\n");
    const char* var = name;
    for (const auto& line : lines) {
    if (!strcmp(name, "board")) var = "product";
        if (line.empty()) {

            continue;
    const char** out = reinterpret_cast<const char**>(malloc(sizeof(char*) * count));
    if (out == nullptr) die("out of memory");

    for (size_t i = 0; i < count; ++i) {
        out[i] = xstrdup(strip(val[i]));
        }
        }


    fb_require(product, var, invert, count, out);
        std::string name;
}
        std::string product;
        bool invert;
        std::vector<std::string> options;


static void check_requirements(char* data, int64_t sz) {
        if (!ParseRequirementLine(line, &name, &product, &invert, &options)) {
    char* s = data;
            fprintf(stderr, "android-info.txt syntax error: %s\n", line.c_str());
    while (sz-- > 0) {
            continue;
        if (*s == '\n') {
        }
            *s++ = 0;
        if (name == "partition-exists") {
            check_requirement(data);
            HandlePartitionExists(options);
            data = s;
        } else {
        } else {
            s++;
            CheckRequirement(cur_product, name, product, invert, options);
        }
        }
    }
    }
}
}
@@ -1156,7 +1193,7 @@ void FlashAllTool::CheckRequirements() {
    if (!source_.ReadFile("android-info.txt", &contents)) {
    if (!source_.ReadFile("android-info.txt", &contents)) {
        die("could not read android-info.txt");
        die("could not read android-info.txt");
    }
    }
    check_requirements(reinterpret_cast<char*>(contents.data()), contents.size());
    ::CheckRequirements({contents.data(), contents.size()});
}
}


void FlashAllTool::DetermineSecondarySlot() {
void FlashAllTool::DetermineSecondarySlot() {
@@ -1265,8 +1302,6 @@ int ZipImageSource::OpenFile(const std::string& name) const {
static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary) {
static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary) {
    dump_info();
    dump_info();


    fb_query_save("product", cur_product, sizeof(cur_product));

    ZipArchiveHandle zip;
    ZipArchiveHandle zip;
    int error = OpenArchive(filename, &zip);
    int error = OpenArchive(filename, &zip);
    if (error != 0) {
    if (error != 0) {
@@ -1302,8 +1337,6 @@ static void do_flashall(const std::string& slot_override, bool skip_secondary, b
    std::string fname;
    std::string fname;
    dump_info();
    dump_info();


    fb_query_save("product", cur_product, sizeof(cur_product));

    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe);
    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe);
    tool.Flash();
    tool.Flash();
}
}
+0 −22
Original line number Original line Diff line number Diff line
@@ -147,28 +147,6 @@ RetCode FastBootDriver::Partitions(std::vector<std::tuple<std::string, uint64_t>
    return SUCCESS;
    return SUCCESS;
}
}


RetCode FastBootDriver::Require(const std::string& var, const std::vector<std::string>& allowed,
                                bool* reqmet, bool invert) {
    *reqmet = invert;
    RetCode ret;
    std::string response;
    if ((ret = GetVar(var, &response))) {
        return ret;
    }

    // Now check if we have a match
    for (const auto s : allowed) {
        // If it ends in *, and starting substring match
        if (response == s || (s.length() && s.back() == '*' &&
                              !response.compare(0, s.length() - 1, s, 0, s.length() - 1))) {
            *reqmet = !invert;
            break;
        }
    }

    return SUCCESS;
}

RetCode FastBootDriver::Download(int fd, size_t size, std::string* response,
RetCode FastBootDriver::Download(int fd, size_t size, std::string* response,
                                 std::vector<std::string>* info) {
                                 std::vector<std::string>* info) {
    RetCode ret;
    RetCode ret;
+142 −0
Original line number Original line Diff line number Diff line
@@ -59,3 +59,145 @@ TEST(FastBoot, ParseOsVersion) {
    EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.128.3"), "bad OS version");
    EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.128.3"), "bad OS version");
    EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.2.128"), "bad OS version");
    EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.2.128"), "bad OS version");
}
}

extern bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product,
                                 bool* invert, std::vector<std::string>* options);

static void ParseRequirementLineTest(const std::string& line, const std::string& expected_name,
                                     const std::string& expected_product, bool expected_invert,
                                     const std::vector<std::string>& expected_options) {
    std::string name;
    std::string product;
    bool invert;
    std::vector<std::string> options;

    EXPECT_TRUE(ParseRequirementLine(line, &name, &product, &invert, &options)) << line;

    EXPECT_EQ(expected_name, name) << line;
    EXPECT_EQ(expected_product, product) << line;
    EXPECT_EQ(expected_invert, invert) << line;
    EXPECT_EQ(expected_options, options) << line;
}

TEST(FastBoot, ParseRequirementLineSuccesses) {
    // Examples provided in the code + slight variations.
    ParseRequirementLineTest("require product=alpha", "product", "", false, {"alpha"});
    ParseRequirementLineTest("require product=alpha|beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require version-bootloader=1234", "version-bootloader", "", false,
                             {"1234"});
    ParseRequirementLineTest("require-for-product:gamma version-bootloader=istanbul",
                             "version-bootloader", "gamma", false, {"istanbul"});
    ParseRequirementLineTest("require-for-product:gamma version-bootloader=istanbul|constantinople",
                             "version-bootloader", "gamma", false, {"istanbul", "constantinople"});
    ParseRequirementLineTest("require partition-exists=vendor", "partition-exists", "", false,
                             {"vendor"});
    ParseRequirementLineTest("reject product=alpha", "product", "", true, {"alpha"});
    ParseRequirementLineTest("reject product=alpha|beta|gamma", "product", "", true,
                             {"alpha", "beta", "gamma"});

    // Without any prefix, assume 'require'
    ParseRequirementLineTest("product=alpha|beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    // Including if the variable name is otherwise a prefix keyword
    ParseRequirementLineTest("require = alpha", "require", "", false, {"alpha"});
    ParseRequirementLineTest("reject = alpha", "reject", "", false, {"alpha"});
    ParseRequirementLineTest("require-for-product:gamma = alpha", "require-for-product:gamma", "",
                             false, {"alpha"});

    // Extra spaces are allowed.
    ParseRequirementLineTest("require    product=alpha|beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product    =alpha|beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product=   alpha|beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product   =   alpha|beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product=alpha  |beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product=alpha|  beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product=alpha  |  beta|gamma", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require product=alpha|beta|gamma   ", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("product  =  alpha  |  beta  |  gamma   ", "product", "", false,
                             {"alpha", "beta", "gamma"});
    ParseRequirementLineTest("require-for-product:  gamma version-bootloader=istanbul",
                             "version-bootloader", "gamma", false, {"istanbul"});

    // Extraneous ending | is okay, implies accepting an empty string.
    ParseRequirementLineTest("require product=alpha|", "product", "", false, {"alpha", ""});
    ParseRequirementLineTest("require product=alpha|beta|gamma|", "product", "", false,
                             {"alpha", "beta", "gamma", ""});

    // Accept empty options, double ||, etc, implies accepting an empty string.
    ParseRequirementLineTest("require product=alpha||beta|   |gamma", "product", "", false,
                             {"alpha", "", "beta", "", "gamma"});
    ParseRequirementLineTest("require product=alpha||beta|gamma", "product", "", false,
                             {"alpha", "", "beta", "gamma"});
    ParseRequirementLineTest("require product=alpha|beta|   |gamma", "product", "", false,
                             {"alpha", "beta", "", "gamma"});
    ParseRequirementLineTest("require product=alpha||", "product", "", false, {"alpha", "", ""});
    ParseRequirementLineTest("require product=alpha|| ", "product", "", false, {"alpha", "", ""});
    ParseRequirementLineTest("require product=alpha| ", "product", "", false, {"alpha", ""});
    ParseRequirementLineTest("require product=alpha|beta| ", "product", "", false,
                             {"alpha", "beta", ""});

    // No option string is also treating as accepting an empty string.
    ParseRequirementLineTest("require =", "require", "", false, {""});
    ParseRequirementLineTest("require = |", "require", "", false, {"", ""});
    ParseRequirementLineTest("reject =", "reject", "", false, {""});
    ParseRequirementLineTest("reject = |", "reject", "", false, {"", ""});
    ParseRequirementLineTest("require-for-product: =", "require-for-product:", "", false, {""});
    ParseRequirementLineTest("require-for-product: = | ", "require-for-product:", "", false,
                             {"", ""});
    ParseRequirementLineTest("require product=", "product", "", false, {""});
    ParseRequirementLineTest("require product = ", "product", "", false, {""});
    ParseRequirementLineTest("require product = | ", "product", "", false, {"", ""});
    ParseRequirementLineTest("reject product=", "product", "", true, {""});
    ParseRequirementLineTest("reject product = ", "product", "", true, {""});
    ParseRequirementLineTest("reject product = | ", "product", "", true, {"", ""});
    ParseRequirementLineTest("require-for-product:gamma product=", "product", "gamma", false, {""});
    ParseRequirementLineTest("require-for-product:gamma product = ", "product", "gamma", false,
                             {""});
    ParseRequirementLineTest("require-for-product:gamma product = |", "product", "gamma", false,
                             {"", ""});

    // Check for board -> product substitution.
    ParseRequirementLineTest("require board=alpha", "product", "", false, {"alpha"});
    ParseRequirementLineTest("board=alpha", "product", "", false, {"alpha"});
}

static void ParseRequirementLineTestMalformed(const std::string& line) {
    std::string name;
    std::string product;
    bool invert;
    std::vector<std::string> options;

    EXPECT_FALSE(ParseRequirementLine(line, &name, &product, &invert, &options)) << line;
}

TEST(FastBoot, ParseRequirementLineMalformed) {
    ParseRequirementLineTestMalformed("nothing");
    ParseRequirementLineTestMalformed("");
    ParseRequirementLineTestMalformed("=");
    ParseRequirementLineTestMalformed("|");

    ParseRequirementLineTestMalformed("require");
    ParseRequirementLineTestMalformed("require ");
    ParseRequirementLineTestMalformed("reject");
    ParseRequirementLineTestMalformed("reject ");
    ParseRequirementLineTestMalformed("require-for-product:");
    ParseRequirementLineTestMalformed("require-for-product: ");

    ParseRequirementLineTestMalformed("require product");
    ParseRequirementLineTestMalformed("reject product");

    ParseRequirementLineTestMalformed("require-for-product:gamma");
    ParseRequirementLineTestMalformed("require-for-product:gamma product");

    // No spaces allowed before between require-for-product and :.
    ParseRequirementLineTestMalformed("require-for-product :");
}
Loading