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

Commit bdbf5047 authored by Tom Cherry's avatar Tom Cherry
Browse files

init: add ctl.oneshot_on/ctl.oneshot_off

Some services are lazy HALs on some platforms and not lazy HALs on
others; this is known at runtime by hwservicemanager, so this change
adds these properties to allow hwservicemanager to turn one oneshot
(for lazy HALs).  It may also be required to make a lazy HAL not lazy
anymore, and oneshot_off is provided for this.

Bug: 147841742
Test: new unit test that turn on and off oneshot on a service (bootanim)
      and observes that it follows the expected behavior
Change-Id: I79524e2c9a5008f90c8d3bc40920fde00602a439
parent 3ab681c9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -240,6 +240,7 @@ cc_test {
        "firmware_handler_test.cpp",
        "init_test.cpp",
        "keychords_test.cpp",
        "oneshot_on_test.cpp",
        "persistent_properties_test.cpp",
        "property_service_test.cpp",
        "property_type_test.cpp",
+24 −12
Original line number Diff line number Diff line
@@ -720,23 +720,35 @@ Init provides state information with the following properties.
  characteristics in a device agnostic manner.

Init responds to properties that begin with `ctl.`.  These properties take the format of
`ctl.<command>` and the _value_ of the system property is used as a parameter, for example:
`SetProperty("ctl.start", "logd")` will run the `start` command on `logd`.  Note that these
`ctl.[<target>_]<command>` and the _value_ of the system property is used as a parameter.  The
_target_ is optional and specifies the service option that _value_ is meant to match with.  There is
only one option for _target_, `interface` which indicates that _value_ will refer to an interface
that a service provides and not the service name itself.

For example:

`SetProperty("ctl.start", "logd")` will run the `start` command on `logd`.

`SetProperty("ctl.interface_start", "aidl/aidl_lazy_test_1")` will run the `start` command on the
service that exposes the `aidl aidl_lazy_test_1` interface.

Note that these
properties are only settable; they will have no value when read.

`ctl.start` \
`ctl.restart` \
`ctl.stop`
> These are equivalent to using the `start`, `restart`, and `stop` commands on the service specified
The _commands_ are listed below.

`start` \
`restart` \
`stop` \
These are equivalent to using the `start`, `restart`, and `stop` commands on the service specified
by the _value_ of the property.

`ctl.interface_start` \
`ctl.interface_restart` \
`ctl.interface_stop`
> These are equivalent to using the `interface_start`, `interface_restart`, and `interface_stop`
commands on the interface specified by the _value_ of the property.
`oneshot_one` and `oneshot_off` will turn on or off the _oneshot_
flag for the service specified by the _value_ of the property.  This is
particularly intended for services that are conditionally lazy HALs.  When
they are lazy HALs, oneshot must be on, otherwise oneshot should be off.

`ctl.sigstop_on` and `ctl.sigstop_off` will turn on or off the _sigstop_ feature for the service
`sigstop_on` and `sigstop_off` will turn on or off the _sigstop_ feature for the service
specified by the _value_ of the property.  See the _Debugging init_ section below for more details
about this feature.

+36 −48
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ using namespace std::chrono_literals;
using namespace std::string_literals;

using android::base::boot_clock;
using android::base::ConsumePrefix;
using android::base::GetProperty;
using android::base::ReadFileToString;
using android::base::SetProperty;
@@ -367,40 +368,27 @@ enum class ControlTarget {
    INTERFACE,  // action gets called for every service that holds this interface
};

struct ControlMessageFunction {
    ControlTarget target;
    std::function<Result<void>(Service*)> action;
};
using ControlMessageFunction = std::function<Result<void>(Service*)>;

static const std::map<std::string, ControlMessageFunction>& get_control_message_map() {
static const std::map<std::string, ControlMessageFunction, std::less<>>& GetControlMessageMap() {
    // clang-format off
    static const std::map<std::string, ControlMessageFunction> control_message_functions = {
        {"sigstop_on",        {ControlTarget::SERVICE,
                               [](auto* service) { service->set_sigstop(true); return Result<void>{}; }}},
        {"sigstop_off",       {ControlTarget::SERVICE,
                               [](auto* service) { service->set_sigstop(false); return Result<void>{}; }}},
        {"start",             {ControlTarget::SERVICE,   DoControlStart}},
        {"stop",              {ControlTarget::SERVICE,   DoControlStop}},
        {"restart",           {ControlTarget::SERVICE,   DoControlRestart}},
        {"interface_start",   {ControlTarget::INTERFACE, DoControlStart}},
        {"interface_stop",    {ControlTarget::INTERFACE, DoControlStop}},
        {"interface_restart", {ControlTarget::INTERFACE, DoControlRestart}},
    static const std::map<std::string, ControlMessageFunction, std::less<>> control_message_functions = {
        {"sigstop_on",        [](auto* service) { service->set_sigstop(true); return Result<void>{}; }},
        {"sigstop_off",       [](auto* service) { service->set_sigstop(false); return Result<void>{}; }},
        {"oneshot_on",        [](auto* service) { service->set_oneshot(true); return Result<void>{}; }},
        {"oneshot_off",       [](auto* service) { service->set_oneshot(false); return Result<void>{}; }},
        {"start",             DoControlStart},
        {"stop",              DoControlStop},
        {"restart",           DoControlRestart},
    };
    // clang-format on

    return control_message_functions;
}

bool HandleControlMessage(const std::string& msg, const std::string& name, pid_t pid) {
    const auto& map = get_control_message_map();
    const auto it = map.find(msg);

    if (it == map.end()) {
        LOG(ERROR) << "Unknown control msg '" << msg << "'";
        return false;
    }

    std::string cmdline_path = StringPrintf("proc/%d/cmdline", pid);
static bool HandleControlMessage(std::string_view message, const std::string& name,
                                 pid_t from_pid) {
    std::string cmdline_path = StringPrintf("proc/%d/cmdline", from_pid);
    std::string process_cmdline;
    if (ReadFileToString(cmdline_path, &process_cmdline)) {
        std::replace(process_cmdline.begin(), process_cmdline.end(), '\0', ' ');
@@ -409,37 +397,37 @@ bool HandleControlMessage(const std::string& msg, const std::string& name, pid_t
        process_cmdline = "unknown process";
    }

    const ControlMessageFunction& function = it->second;

    Service* svc = nullptr;
    Service* service = nullptr;
    auto action = message;
    if (ConsumePrefix(&action, "interface_")) {
        service = ServiceList::GetInstance().FindInterface(name);
    } else {
        service = ServiceList::GetInstance().FindService(name);
    }

    switch (function.target) {
        case ControlTarget::SERVICE:
            svc = ServiceList::GetInstance().FindService(name);
            break;
        case ControlTarget::INTERFACE:
            svc = ServiceList::GetInstance().FindInterface(name);
            break;
        default:
            LOG(ERROR) << "Invalid function target from static map key ctl." << msg << ": "
                       << static_cast<std::underlying_type<ControlTarget>::type>(function.target);
    if (service == nullptr) {
        LOG(ERROR) << "Control message: Could not find '" << name << "' for ctl." << message
                   << " from pid: " << from_pid << " (" << process_cmdline << ")";
        return false;
    }

    if (svc == nullptr) {
        LOG(ERROR) << "Control message: Could not find '" << name << "' for ctl." << msg
                   << " from pid: " << pid << " (" << process_cmdline << ")";
    const auto& map = GetControlMessageMap();
    const auto it = map.find(action);
    if (it == map.end()) {
        LOG(ERROR) << "Unknown control msg '" << message << "'";
        return false;
    }
    const auto& function = it->second;

    if (auto result = function.action(svc); !result.ok()) {
        LOG(ERROR) << "Control message: Could not ctl." << msg << " for '" << name
                   << "' from pid: " << pid << " (" << process_cmdline << "): " << result.error();
    if (auto result = function(service); !result.ok()) {
        LOG(ERROR) << "Control message: Could not ctl." << message << " for '" << name
                   << "' from pid: " << from_pid << " (" << process_cmdline
                   << "): " << result.error();
        return false;
    }

    LOG(INFO) << "Control message: Processed ctl." << msg << " for '" << name
              << "' from pid: " << pid << " (" << process_cmdline << ")";
    LOG(INFO) << "Control message: Processed ctl." << message << " for '" << name
              << "' from pid: " << from_pid << " (" << process_cmdline << ")";
    return true;
}

+44 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#include <gtest/gtest.h>

#include <chrono>

#include <android-base/properties.h>

using android::base::GetProperty;
using android::base::SetProperty;
using android::base::WaitForProperty;
using namespace std::literals;

TEST(init, oneshot_on) {
    // Bootanim shouldn't be running once the device has booted.
    ASSERT_EQ("stopped", GetProperty("init.svc.bootanim", ""));

    SetProperty("ctl.oneshot_off", "bootanim");
    SetProperty("ctl.start", "bootanim");

    // Bootanim exits quickly when the device is fully booted, so check that it goes back to the
    // 'restarting' state that non-oneshot services enter once they've restarted.
    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "restarting", 10s));

    SetProperty("ctl.oneshot_on", "bootanim");
    SetProperty("ctl.start", "bootanim");

    // Now that oneshot is enabled again, bootanim should transition into the 'stopped' state.
    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "stopped", 10s));
}
+7 −0
Original line number Diff line number Diff line
@@ -130,6 +130,13 @@ class Service {
    bool is_updatable() const { return updatable_; }
    bool is_post_data() const { return post_data_; }
    bool is_from_apex() const { return from_apex_; }
    void set_oneshot(bool value) {
        if (value) {
            flags_ |= SVC_ONESHOT;
        } else {
            flags_ &= ~SVC_ONESHOT;
        }
    }

  private:
    void NotifyStateChange(const std::string& new_state) const;