1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "task_profiles.h"
18 #include <android-base/logging.h>
19 #include <gtest/gtest.h>
20 #include <mntent.h>
21 #include <processgroup/processgroup.h>
22 #include <stdio.h>
23 #include <unistd.h>
24
25 #include <fstream>
26
27 using ::android::base::ERROR;
28 using ::android::base::LogFunction;
29 using ::android::base::LogId;
30 using ::android::base::LogSeverity;
31 using ::android::base::SetLogger;
32 using ::android::base::VERBOSE;
33 using ::testing::TestWithParam;
34 using ::testing::Values;
35
36 namespace {
37
IsCgroupV2Mounted()38 bool IsCgroupV2Mounted() {
39 std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
40 if (!mnts) {
41 LOG(ERROR) << "Failed to open /proc/mounts";
42 return false;
43 }
44 struct mntent* mnt;
45 while ((mnt = getmntent(mnts.get()))) {
46 if (strcmp(mnt->mnt_fsname, "cgroup2") == 0) {
47 return true;
48 }
49 }
50 return false;
51 }
52
53 class ScopedLogCapturer {
54 public:
55 struct log_args {
56 LogId log_buffer_id;
57 LogSeverity severity;
58 std::string tag;
59 std::string file;
60 unsigned int line;
61 std::string message;
62 };
63
64 // Constructor. Installs a new logger and saves the currently active logger.
ScopedLogCapturer()65 ScopedLogCapturer() {
66 saved_severity_ = SetMinimumLogSeverity(android::base::VERBOSE);
67 saved_logger_ = SetLogger([this](LogId log_buffer_id, LogSeverity severity, const char* tag,
68 const char* file, unsigned int line, const char* message) {
69 if (saved_logger_) {
70 saved_logger_(log_buffer_id, severity, tag, file, line, message);
71 }
72 log_.emplace_back(log_args{.log_buffer_id = log_buffer_id,
73 .severity = severity,
74 .tag = tag,
75 .file = file,
76 .line = line,
77 .message = message});
78 });
79 }
80 // Destructor. Restores the original logger and log level.
~ScopedLogCapturer()81 ~ScopedLogCapturer() {
82 SetLogger(std::move(saved_logger_));
83 SetMinimumLogSeverity(saved_severity_);
84 }
85 ScopedLogCapturer(const ScopedLogCapturer&) = delete;
86 ScopedLogCapturer& operator=(const ScopedLogCapturer&) = delete;
87 // Returns the logged lines.
Log() const88 const std::vector<log_args>& Log() const { return log_; }
89
90 private:
91 LogSeverity saved_severity_;
92 LogFunction saved_logger_;
93 std::vector<log_args> log_;
94 };
95
96 // cgroup attribute at the top level of the cgroup hierarchy.
97 class ProfileAttributeMock : public IProfileAttribute {
98 public:
ProfileAttributeMock(const std::string & file_name)99 ProfileAttributeMock(const std::string& file_name) : file_name_(file_name) {}
100 ~ProfileAttributeMock() override = default;
Reset(const CgroupController & controller,const std::string & file_name)101 void Reset(const CgroupController& controller, const std::string& file_name) override {
102 CHECK(false);
103 }
controller() const104 const CgroupController* controller() const override {
105 CHECK(false);
106 return {};
107 }
file_name() const108 const std::string& file_name() const override { return file_name_; }
GetPathForTask(int tid,std::string * path) const109 bool GetPathForTask(int tid, std::string* path) const override {
110 #ifdef __ANDROID__
111 CHECK(CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, path));
112 CHECK_GT(path->length(), 0);
113 if (path->rbegin()[0] != '/') {
114 *path += "/";
115 }
116 #else
117 // Not Android.
118 *path = "/sys/fs/cgroup/";
119 #endif
120 *path += file_name_;
121 return true;
122 };
123
124 private:
125 const std::string file_name_;
126 };
127
128 struct TestParam {
129 const char* attr_name;
130 const char* attr_value;
131 bool optional_attr;
132 bool result;
133 LogSeverity log_severity;
134 const char* log_prefix;
135 const char* log_suffix;
136 };
137
138 class SetAttributeFixture : public TestWithParam<TestParam> {
139 public:
140 ~SetAttributeFixture() = default;
141 };
142
TEST_P(SetAttributeFixture,SetAttribute)143 TEST_P(SetAttributeFixture, SetAttribute) {
144 // Treehugger runs host tests inside a container without cgroupv2 support.
145 if (!IsCgroupV2Mounted()) {
146 GTEST_SKIP();
147 return;
148 }
149 const TestParam params = GetParam();
150 ScopedLogCapturer captured_log;
151 ProfileAttributeMock pa(params.attr_name);
152 SetAttributeAction a(&pa, params.attr_value, params.optional_attr);
153 EXPECT_EQ(a.ExecuteForProcess(getuid(), getpid()), params.result);
154 auto log = captured_log.Log();
155 if (params.log_prefix || params.log_suffix) {
156 ASSERT_EQ(log.size(), 1);
157 EXPECT_EQ(log[0].severity, params.log_severity);
158 if (params.log_prefix) {
159 EXPECT_EQ(log[0].message.find(params.log_prefix), 0);
160 }
161 if (params.log_suffix) {
162 EXPECT_NE(log[0].message.find(params.log_suffix), std::string::npos);
163 }
164 } else {
165 ASSERT_EQ(log.size(), 0);
166 }
167 }
168
169 // Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
170 // exists }.
171 INSTANTIATE_TEST_SUITE_P(
172 SetAttributeTestSuite, SetAttributeFixture,
173 Values(
174 // Test that attempting to write into a non-existing cgroup attribute fails and also
175 // that an error message is logged.
176 TestParam{.attr_name = "no-such-attribute",
177 .attr_value = ".",
178 .optional_attr = false,
179 .result = false,
180 .log_severity = ERROR,
181 .log_prefix = "No such cgroup attribute"},
182 // Test that attempting to write into an optional non-existing cgroup attribute
183 // results in the return value 'true' and also that no messages are logged.
184 TestParam{.attr_name = "no-such-attribute",
185 .attr_value = ".",
186 .optional_attr = true,
187 .result = true},
188 // Test that attempting to write an invalid value into an existing optional cgroup
189 // attribute fails and also that it causes an error
190 // message to be logged.
191 TestParam{.attr_name = "cgroup.procs",
192 .attr_value = "-1",
193 .optional_attr = true,
194 .result = false,
195 .log_severity = ERROR,
196 .log_prefix = "Failed to write",
197 .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"},
198 // Test that attempting to write into an existing optional read-only cgroup
199 // attribute fails and also that it causes an error message to be logged.
200 TestParam{
201 .attr_name = "cgroup.controllers",
202 .attr_value = ".",
203 .optional_attr = false,
204 .result = false,
205 .log_severity = ERROR,
206 .log_prefix = "Failed to write",
207 .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
208
209 } // namespace
210