• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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