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

Commit 1693f420 authored by Bart Van Assche's avatar Bart Van Assche
Browse files

init: Introduce class InterprocessFifo



Prepare for introducing a second interprocess communication channel by
introducing the class InterprocessFifo. Stop using std::unique_ptr<> for
holding the pipe file descriptors. Handle EOF consistently.

Bug: 213617178
Change-Id: Ic0cf18d3d8ea61b8ee17e64de8a9df2736e26728
Signed-off-by: default avatarBart Van Assche <bvanassche@google.com>
parent ce45c8ff
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ init_common_sources = [
    "epoll.cpp",
    "import_parser.cpp",
    "interface_utils.cpp",
    "interprocess_fifo.cpp",
    "keychords.cpp",
    "parser.cpp",
    "property_type.cpp",
@@ -467,6 +468,7 @@ cc_test {
        "epoll_test.cpp",
        "firmware_handler_test.cpp",
        "init_test.cpp",
        "interprocess_fifo_test.cpp",
        "keychords_test.cpp",
        "oneshot_on_test.cpp",
        "persistent_properties_test.cpp",
@@ -481,7 +483,10 @@ cc_test {
        "ueventd_test.cpp",
        "util_test.cpp",
    ],
    static_libs: ["libinit"],
    static_libs: [
        "libgmock",
        "libinit",
    ],

    test_suites: [
        "cts",
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 "interprocess_fifo.h"

#include <android-base/logging.h>

#include <unistd.h>

using ::android::base::ErrnoError;
using ::android::base::Error;
using ::android::base::Result;

namespace android {
namespace init {

InterprocessFifo::InterprocessFifo() noexcept : fds_({-1, -1}) {}

InterprocessFifo::InterprocessFifo(InterprocessFifo&& orig) noexcept : fds_({-1, -1}) {
    std::swap(fds_, orig.fds_);
}

InterprocessFifo::~InterprocessFifo() noexcept {
    Close();
}

void InterprocessFifo::CloseFd(int& fd) noexcept {
    if (fd >= 0) {
        close(fd);
        fd = -1;
    }
}

void InterprocessFifo::CloseReadFd() noexcept {
    CloseFd(fds_[0]);
}

void InterprocessFifo::CloseWriteFd() noexcept {
    CloseFd(fds_[1]);
}

void InterprocessFifo::Close() noexcept {
    CloseReadFd();
    CloseWriteFd();
}

Result<void> InterprocessFifo::Initialize() noexcept {
    if (fds_[0] >= 0) {
        return Error() << "already initialized";
    }
    if (pipe(fds_.data()) < 0) {  // NOLINT(android-cloexec-pipe)
        return ErrnoError() << "pipe()";
    }
    return {};
}

Result<uint8_t> InterprocessFifo::Read() noexcept {
    uint8_t byte;
    ssize_t count = read(fds_[0], &byte, 1);
    if (count < 0) {
        return ErrnoError() << "read()";
    }
    if (count == 0) {
        return Error() << "read() EOF";
    }
    DCHECK_EQ(count, 1);
    return byte;
}

Result<void> InterprocessFifo::Write(uint8_t byte) noexcept {
    ssize_t written = write(fds_[1], &byte, 1);
    if (written < 0) {
        return ErrnoError() << "write()";
    }
    if (written == 0) {
        return Error() << "write() EOF";
    }
    DCHECK_EQ(written, 1);
    return {};
}

}  // namespace init
}  // namespace android
+52 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 <array>

#include <android-base/result.h>

namespace android {
namespace init {

// A FIFO for inter-process communication that uses a Unix pipe internally.
class InterprocessFifo {
  public:
    template <typename T>
    using Result = ::android::base::Result<T>;

    InterprocessFifo() noexcept;
    InterprocessFifo(const InterprocessFifo& orig) noexcept = delete;
    InterprocessFifo(InterprocessFifo&& orig) noexcept;
    InterprocessFifo& operator=(const InterprocessFifo& orig) noexcept = delete;
    InterprocessFifo& operator=(InterprocessFifo&& orig) noexcept = delete;
    ~InterprocessFifo() noexcept;
    void CloseReadFd() noexcept;
    void CloseWriteFd() noexcept;
    void Close() noexcept;
    Result<void> Initialize() noexcept;
    Result<void> Write(uint8_t byte) noexcept;
    Result<uint8_t> Read() noexcept;

  private:
    static void CloseFd(int& fd) noexcept;

    std::array<int, 2> fds_;
};

}  // namespace init
}  // namespace android
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 "interprocess_fifo.h"

#include <android-base/result-gmock.h>
#include <gtest/gtest.h>

#define ASSERT_OK(e) ASSERT_THAT(e, Ok())
#define ASSERT_NOT_OK(e) ASSERT_THAT(e, Not(Ok()))

using ::android::base::Result;
using ::android::base::testing::Ok;
using ::testing::Not;

namespace android {
namespace init {

TEST(FifoTest, WriteAndRead) {
    InterprocessFifo fifo;
    ASSERT_OK(fifo.Initialize());
    ASSERT_OK(fifo.Write('a'));
    ASSERT_OK(fifo.Write('b'));
    Result<uint8_t> result = fifo.Read();
    ASSERT_OK(result);
    EXPECT_EQ(*result, 'a');
    result = fifo.Read();
    ASSERT_OK(result);
    EXPECT_EQ(*result, 'b');
    InterprocessFifo fifo2 = std::move(fifo);
    ASSERT_NOT_OK(fifo.Write('c'));
    ASSERT_NOT_OK(fifo.Read());
    ASSERT_OK(fifo2.Write('d'));
    result = fifo2.Read();
    ASSERT_OK(result);
    EXPECT_EQ(*result, 'd');
}

}  // namespace init
}  // namespace android
+20 −23
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@

#include <string>

#include "interprocess_fifo.h"
#include "lmkd_service.h"
#include "service_list.h"
#include "util.h"
@@ -442,14 +443,6 @@ Result<void> Service::ExecStart() {
    return {};
}

static void ClosePipe(const std::array<int, 2>* pipe) {
    for (const auto fd : *pipe) {
        if (fd >= 0) {
            close(fd);
        }
    }
}

Result<void> Service::CheckConsole() {
    if (!(flags_ & SVC_CONSOLE)) {
        return {};
@@ -514,7 +507,7 @@ void Service::ConfigureMemcg() {

// Enters namespaces, sets environment variables, writes PID files and runs the service executable.
void Service::RunService(const std::vector<Descriptor>& descriptors,
                         std::unique_ptr<std::array<int, 2>, decltype(&ClosePipe)> pipefd) {
                         InterprocessFifo cgroups_activated) {
    if (auto result = EnterNamespaces(namespaces_, name_, mount_namespace_); !result.ok()) {
        LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error();
    }
@@ -536,12 +529,12 @@ void Service::RunService(const std::vector<Descriptor>& descriptors,

    // Wait until the cgroups have been created and until the cgroup controllers have been
    // activated.
    char byte = 0;
    if (read((*pipefd)[0], &byte, 1) < 0) {
        PLOG(ERROR) << "failed to read from notification channel";
    Result<uint8_t> byte = cgroups_activated.Read();
    if (!byte.ok()) {
        LOG(ERROR) << name_ << ": failed to read from notification channel: " << byte.error();
    }
    pipefd.reset();
    if (!byte) {
    cgroups_activated.Close();
    if (!*byte) {
        LOG(FATAL) << "Service '" << name_  << "' failed to start due to a fatal error";
        _exit(EXIT_FAILURE);
    }
@@ -605,10 +598,10 @@ Result<void> Service::Start() {
        return {};
    }

    std::unique_ptr<std::array<int, 2>, decltype(&ClosePipe)> pipefd(new std::array<int, 2>{-1, -1},
                                                                     ClosePipe);
    if (pipe(pipefd->data()) < 0) {
        return ErrnoError() << "pipe()";
    InterprocessFifo cgroups_activated;

    if (Result<void> result = cgroups_activated.Initialize(); !result.ok()) {
        return result;
    }

    if (Result<void> result = CheckConsole(); !result.ok()) {
@@ -667,8 +660,11 @@ Result<void> Service::Start() {

    if (pid == 0) {
        umask(077);
        RunService(descriptors, std::move(pipefd));
        cgroups_activated.CloseWriteFd();
        RunService(descriptors, std::move(cgroups_activated));
        _exit(127);
    } else {
        cgroups_activated.CloseReadFd();
    }

    if (pid < 0) {
@@ -697,8 +693,9 @@ Result<void> Service::Start() {
                         limit_percent_ != -1 || !limit_property_.empty();
        errno = -createProcessGroup(proc_attr_.uid, pid_, use_memcg);
        if (errno != 0) {
            if (char byte = 0; write((*pipefd)[1], &byte, 1) < 0) {
                return ErrnoError() << "sending notification failed";
            Result<void> result = cgroups_activated.Write(0);
            if (!result.ok()) {
                return Error() << "Sending notification failed: " << result.error();
            }
            return Error() << "createProcessGroup(" << proc_attr_.uid << ", " << pid_
                           << ") failed for service '" << name_ << "'";
@@ -720,8 +717,8 @@ Result<void> Service::Start() {
        LmkdRegister(name_, proc_attr_.uid, pid_, oom_score_adjust_);
    }

    if (char byte = 1; write((*pipefd)[1], &byte, 1) < 0) {
        return ErrnoError() << "sending notification failed";
    if (Result<void> result = cgroups_activated.Write(1); !result.ok()) {
        return Error() << "Sending cgroups activated notification failed: " << result.error();
    }

    NotifyStateChange("running");
Loading