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

Commit 2b580717 authored by Bart Van Assche's avatar Bart Van Assche Committed by Automerger Merge Worker
Browse files

Merge "Add support for optional cgroup attributes" am: c9b78e95

Original change: https://android-review.googlesource.com/c/platform/system/core/+/1962323

Change-Id: I88dbc826e6b0439e7fa8384c6287719de6068e0b
parents aae9c4ec c9b78e95
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -73,3 +73,29 @@ cc_library {
    ],
    min_sdk_version: "29",
}

cc_test {
    name: "task_profiles_test",
    host_supported: true,
    cflags: [
        "-Wall",
        "-Werror",
        "-Wexit-time-destructors",
        "-Wno-unused-parameter",
    ],
    srcs: [
        "task_profiles_test.cpp",
    ],
    header_libs: [
        "libcutils_headers",
        "libprocessgroup_headers",
    ],
    shared_libs: [
        "libbase",
        "libcgrouprc",
        "libprocessgroup",
    ],
    static_libs: [
        "libgmock",
    ],
}
+2 −1
Original line number Diff line number Diff line
@@ -25,11 +25,12 @@ message TaskProfiles {
    repeated AggregateProfiles aggregateprofiles = 3 [json_name = "AggregateProfiles"];
}

// Next: 4
// Next: 5
message Attribute {
    string name = 1 [json_name = "Name"];
    string controller = 2 [json_name = "Controller"];
    string file = 3 [json_name = "File"];
    string optional = 4 [json_name = "Optional"];
}

// Next: 3
+11 −2
Original line number Diff line number Diff line
@@ -206,6 +206,14 @@ bool SetAttributeAction::ExecuteForTask(int tid) const {
    }

    if (!WriteStringToFile(value_, path)) {
        if (errno == ENOENT) {
            if (optional_) {
                return true;
            } else {
                LOG(ERROR) << "No such cgroup attribute: " << path;
                return false;
            }
        }
        PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
        return false;
    }
@@ -675,11 +683,12 @@ bool TaskProfiles::Load(const CgroupMap& cg_map, const std::string& file_name) {
            } else if (action_name == "SetAttribute") {
                std::string attr_name = params_val["Name"].asString();
                std::string attr_value = params_val["Value"].asString();
                bool optional = strcmp(params_val["Optional"].asString().c_str(), "true") == 0;

                auto iter = attributes_.find(attr_name);
                if (iter != attributes_.end()) {
                    profile->Add(
                            std::make_unique<SetAttributeAction>(iter->second.get(), attr_value));
                    profile->Add(std::make_unique<SetAttributeAction>(iter->second.get(),
                                                                      attr_value, optional));
                } else {
                    LOG(WARNING) << "SetAttribute: unknown attribute: " << attr_name;
                }
+3 −2
Original line number Diff line number Diff line
@@ -102,8 +102,8 @@ class SetTimerSlackAction : public ProfileAction {
// Set attribute profile element
class SetAttributeAction : public ProfileAction {
  public:
    SetAttributeAction(const IProfileAttribute* attribute, const std::string& value)
        : attribute_(attribute), value_(value) {}
    SetAttributeAction(const IProfileAttribute* attribute, const std::string& value, bool optional)
        : attribute_(attribute), value_(value), optional_(optional) {}

    const char* Name() const override { return "SetAttribute"; }
    bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
@@ -112,6 +112,7 @@ class SetAttributeAction : public ProfileAction {
  private:
    const IProfileAttribute* attribute_;
    std::string value_;
    bool optional_;
};

// Set cgroup profile element
+209 −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 "task_profiles.h"
#include <android-base/logging.h>
#include <gtest/gtest.h>
#include <mntent.h>
#include <processgroup/processgroup.h>
#include <stdio.h>
#include <unistd.h>

#include <fstream>

using ::android::base::ERROR;
using ::android::base::LogFunction;
using ::android::base::LogId;
using ::android::base::LogSeverity;
using ::android::base::SetLogger;
using ::android::base::VERBOSE;
using ::testing::TestWithParam;
using ::testing::Values;

namespace {

bool IsCgroupV2Mounted() {
    std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
    if (!mnts) {
        LOG(ERROR) << "Failed to open /proc/mounts";
        return false;
    }
    struct mntent* mnt;
    while ((mnt = getmntent(mnts.get()))) {
        if (strcmp(mnt->mnt_fsname, "cgroup2") == 0) {
            return true;
        }
    }
    return false;
}

class ScopedLogCapturer {
  public:
    struct log_args {
        LogId log_buffer_id;
        LogSeverity severity;
        std::string tag;
        std::string file;
        unsigned int line;
        std::string message;
    };

    // Constructor. Installs a new logger and saves the currently active logger.
    ScopedLogCapturer() {
        saved_severity_ = SetMinimumLogSeverity(android::base::VERBOSE);
        saved_logger_ = SetLogger([this](LogId log_buffer_id, LogSeverity severity, const char* tag,
                                         const char* file, unsigned int line, const char* message) {
            if (saved_logger_) {
                saved_logger_(log_buffer_id, severity, tag, file, line, message);
            }
            log_.emplace_back(log_args{.log_buffer_id = log_buffer_id,
                                       .severity = severity,
                                       .tag = tag,
                                       .file = file,
                                       .line = line,
                                       .message = message});
        });
    }
    // Destructor. Restores the original logger and log level.
    ~ScopedLogCapturer() {
        SetLogger(std::move(saved_logger_));
        SetMinimumLogSeverity(saved_severity_);
    }
    ScopedLogCapturer(const ScopedLogCapturer&) = delete;
    ScopedLogCapturer& operator=(const ScopedLogCapturer&) = delete;
    // Returns the logged lines.
    const std::vector<log_args>& Log() const { return log_; }

  private:
    LogSeverity saved_severity_;
    LogFunction saved_logger_;
    std::vector<log_args> log_;
};

// cgroup attribute at the top level of the cgroup hierarchy.
class ProfileAttributeMock : public IProfileAttribute {
  public:
    ProfileAttributeMock(const std::string& file_name) : file_name_(file_name) {}
    ~ProfileAttributeMock() override = default;
    void Reset(const CgroupController& controller, const std::string& file_name) override {
        CHECK(false);
    }
    const CgroupController* controller() const override {
        CHECK(false);
        return {};
    }
    const std::string& file_name() const override { return file_name_; }
    bool GetPathForTask(int tid, std::string* path) const override {
#ifdef __ANDROID__
        CHECK(CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, path));
        CHECK_GT(path->length(), 0);
        if (path->rbegin()[0] != '/') {
            *path += "/";
        }
#else
        // Not Android.
        *path = "/sys/fs/cgroup/";
#endif
        *path += file_name_;
        return true;
    };

  private:
    const std::string file_name_;
};

struct TestParam {
    const char* attr_name;
    const char* attr_value;
    bool optional_attr;
    bool result;
    LogSeverity log_severity;
    const char* log_prefix;
    const char* log_suffix;
};

class SetAttributeFixture : public TestWithParam<TestParam> {
  public:
    ~SetAttributeFixture() = default;
};

TEST_P(SetAttributeFixture, SetAttribute) {
    // Treehugger runs host tests inside a container without cgroupv2 support.
    if (!IsCgroupV2Mounted()) {
        GTEST_SKIP();
        return;
    }
    const TestParam params = GetParam();
    ScopedLogCapturer captured_log;
    ProfileAttributeMock pa(params.attr_name);
    SetAttributeAction a(&pa, params.attr_value, params.optional_attr);
    EXPECT_EQ(a.ExecuteForProcess(getuid(), getpid()), params.result);
    auto log = captured_log.Log();
    if (params.log_prefix || params.log_suffix) {
        ASSERT_EQ(log.size(), 1);
        EXPECT_EQ(log[0].severity, params.log_severity);
        if (params.log_prefix) {
            EXPECT_EQ(log[0].message.find(params.log_prefix), 0);
        }
        if (params.log_suffix) {
            EXPECT_NE(log[0].message.find(params.log_suffix), std::string::npos);
        }
    } else {
        ASSERT_EQ(log.size(), 0);
    }
}

// Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
// exists }.
INSTANTIATE_TEST_SUITE_P(
        SetAttributeTestSuite, SetAttributeFixture,
        Values(
                // Test that attempting to write into a non-existing cgroup attribute fails and also
                // that an error message is logged.
                TestParam{.attr_name = "no-such-attribute",
                          .attr_value = ".",
                          .optional_attr = false,
                          .result = false,
                          .log_severity = ERROR,
                          .log_prefix = "No such cgroup attribute"},
                // Test that attempting to write into an optional non-existing cgroup attribute
                // results in the return value 'true' and also that no messages are logged.
                TestParam{.attr_name = "no-such-attribute",
                          .attr_value = ".",
                          .optional_attr = true,
                          .result = true},
                // Test that attempting to write an invalid value into an existing optional cgroup
                // attribute fails and also that it causes an error
                // message to be logged.
                TestParam{.attr_name = "cgroup.procs",
                          .attr_value = "-1",
                          .optional_attr = true,
                          .result = false,
                          .log_severity = ERROR,
                          .log_prefix = "Failed to write",
                          .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"},
                // Test that attempting to write into an existing optional read-only cgroup
                // attribute fails and also that it causes an error message to be logged.
                TestParam{
                        .attr_name = "cgroup.controllers",
                        .attr_value = ".",
                        .optional_attr = false,
                        .result = false,
                        .log_severity = ERROR,
                        .log_prefix = "Failed to write",
                        .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));

}  // namespace