diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp index d40be9f9e..33e00bc5d 100644 --- a/libprocessgroup/Android.bp +++ b/libprocessgroup/Android.bp @@ -84,6 +84,7 @@ cc_library { header_libs: [ "libcutils_headers", "libprocessgroup_headers", + "libprocessgroup_util", ], export_include_dirs: ["include"], export_header_lib_headers: [ diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp index ebc059944..52b5afe5e 100644 --- a/libprocessgroup/cgroup_map.cpp +++ b/libprocessgroup/cgroup_map.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using android::base::StartsWith; using android::base::StringPrintf; @@ -216,7 +217,13 @@ int CgroupMap::ActivateControllers(const std::string& path) const { for (uint32_t i = 0; i < controller_count; ++i) { const ACgroupController* controller = ACgroupFile_getController(i); const uint32_t flags = ACgroupController_getFlags(controller); - if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) { + uint32_t max_activation_depth = UINT32_MAX; + if (__builtin_available(android 36, *)) { + max_activation_depth = ACgroupController_getMaxActivationDepth(controller); + } + const int depth = util::GetCgroupDepth(ACgroupController_getPath(controller), path); + + if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) { std::string str("+"); str.append(ACgroupController_getName(controller)); if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) { diff --git a/libprocessgroup/cgrouprc/cgroup_controller.cpp b/libprocessgroup/cgrouprc/cgroup_controller.cpp index 5a326e55d..889b3becf 100644 --- a/libprocessgroup/cgrouprc/cgroup_controller.cpp +++ b/libprocessgroup/cgrouprc/cgroup_controller.cpp @@ -32,6 +32,11 @@ uint32_t ACgroupController_getFlags(const ACgroupController* controller) { return controller->flags(); } +uint32_t ACgroupController_getMaxActivationDepth(const ACgroupController* controller) { + CHECK(controller != nullptr); + return controller->max_activation_depth(); +} + const char* ACgroupController_getName(const ACgroupController* controller) { CHECK(controller != nullptr); return controller->name(); diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h index e704a36aa..3a57df547 100644 --- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h +++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h @@ -78,6 +78,14 @@ __attribute__((warn_unused_result)) uint32_t ACgroupController_getVersion(const __attribute__((warn_unused_result, weak)) uint32_t ACgroupController_getFlags( const ACgroupController*) __INTRODUCED_IN(30); +/** + * Returns the maximum activation depth of the given controller. + * Only applicable to cgroup v2 controllers. + * Returns UINT32_MAX if no maximum activation depth is set. + */ +__attribute__((warn_unused_result, weak)) uint32_t ACgroupController_getMaxActivationDepth( + const ACgroupController* controller) __INTRODUCED_IN(36); + /** * Returns the name of the given controller. * If the given controller is null, return nullptr. diff --git a/libprocessgroup/cgrouprc/libcgrouprc.map.txt b/libprocessgroup/cgrouprc/libcgrouprc.map.txt index b62b10f3b..30bd25f18 100644 --- a/libprocessgroup/cgrouprc/libcgrouprc.map.txt +++ b/libprocessgroup/cgrouprc/libcgrouprc.map.txt @@ -16,3 +16,10 @@ LIBCGROUPRC_30 { # introduced=30 local: *; }; + +LIBCGROUPRC_36 { # introduced=36 + global: + ACgroupController_getMaxActivationDepth; # llndk=202504 systemapi + local: + *; +}; diff --git a/libprocessgroup/cgrouprc_format/cgroup_controller.cpp b/libprocessgroup/cgrouprc_format/cgroup_controller.cpp index 56e67df59..0dd909a29 100644 --- a/libprocessgroup/cgrouprc_format/cgroup_controller.cpp +++ b/libprocessgroup/cgrouprc_format/cgroup_controller.cpp @@ -21,13 +21,11 @@ namespace cgrouprc { namespace format { CgroupController::CgroupController(uint32_t version, uint32_t flags, const std::string& name, - const std::string& path) -{ + const std::string& path, uint32_t max_activation_depth) + : version_(version), flags_(flags), max_activation_depth_(max_activation_depth) { // strlcpy isn't available on host. Although there is an implementation // in licutils, libcutils itself depends on libcgrouprc_format, causing // a circular dependency. - version_ = version; - flags_ = flags; strncpy(name_, name.c_str(), sizeof(name_) - 1); name_[sizeof(name_) - 1] = '\0'; strncpy(path_, path.c_str(), sizeof(path_) - 1); @@ -42,6 +40,10 @@ uint32_t CgroupController::flags() const { return flags_; } +uint32_t CgroupController::max_activation_depth() const { + return max_activation_depth_; +} + const char* CgroupController::name() const { return name_; } diff --git a/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h b/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h index 9427a1cf9..c0c1f6034 100644 --- a/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h +++ b/libprocessgroup/cgrouprc_format/include/processgroup/format/cgroup_controller.h @@ -29,10 +29,11 @@ struct CgroupController { public: CgroupController() = default; CgroupController(uint32_t version, uint32_t flags, const std::string& name, - const std::string& path); + const std::string& path, uint32_t max_activation_depth); uint32_t version() const; uint32_t flags() const; + uint32_t max_activation_depth() const; const char* name() const; const char* path() const; @@ -44,6 +45,7 @@ struct CgroupController { uint32_t version_ = 0; uint32_t flags_ = 0; + uint32_t max_activation_depth_ = UINT32_MAX; char name_[CGROUP_NAME_BUF_SZ] = {}; char path_[CGROUP_PATH_BUF_SZ] = {}; }; diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto index f2de3452a..d2fd472d1 100644 --- a/libprocessgroup/profiles/cgroups.proto +++ b/libprocessgroup/profiles/cgroups.proto @@ -24,7 +24,7 @@ message Cgroups { Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"]; } -// Next: 8 +// Next: 9 message Cgroup { string controller = 1 [json_name = "Controller"]; string path = 2 [json_name = "Path"]; @@ -36,6 +36,7 @@ message Cgroup { // https://developers.google.com/protocol-buffers/docs/proto3#default bool needs_activation = 6 [json_name = "NeedsActivation"]; bool is_optional = 7 [json_name = "Optional"]; + uint32 max_activation_depth = 8 [json_name = "MaxActivationDepth"]; } // Next: 6 diff --git a/libprocessgroup/setup/Android.bp b/libprocessgroup/setup/Android.bp index 1e0783ab0..76f0a11f1 100644 --- a/libprocessgroup/setup/Android.bp +++ b/libprocessgroup/setup/Android.bp @@ -37,6 +37,7 @@ cc_library_shared { ], header_libs: [ "libprocessgroup_headers", + "libprocessgroup_util", ], export_header_lib_headers: [ "libprocessgroup_headers", diff --git a/libprocessgroup/setup/cgroup_descriptor.h b/libprocessgroup/setup/cgroup_descriptor.h index 9982bfc73..06ce186fd 100644 --- a/libprocessgroup/setup/cgroup_descriptor.h +++ b/libprocessgroup/setup/cgroup_descriptor.h @@ -30,7 +30,8 @@ namespace cgrouprc { class CgroupDescriptor { public: CgroupDescriptor(uint32_t version, const std::string& name, const std::string& path, - mode_t mode, const std::string& uid, const std::string& gid, uint32_t flags); + mode_t mode, const std::string& uid, const std::string& gid, uint32_t flags, + uint32_t max_activation_depth); const format::CgroupController* controller() const { return &controller_; } mode_t mode() const { return mode_; } diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp index 1b26fbcc6..bd4187475 100644 --- a/libprocessgroup/setup/cgroup_map_write.cpp +++ b/libprocessgroup/setup/cgroup_map_write.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "../build_flags.h" #include "cgroup_descriptor.h" @@ -173,9 +174,15 @@ static void MergeCgroupToDescriptors(std::map* de controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL; } + uint32_t max_activation_depth = UINT32_MAX; + if (cgroup.isMember("MaxActivationDepth")) { + max_activation_depth = cgroup["MaxActivationDepth"].asUInt(); + } + CgroupDescriptor descriptor( cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8), - cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags); + cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags, + max_activation_depth); auto iter = descriptors->find(name); if (iter == descriptors->end()) { @@ -324,7 +331,8 @@ static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) { return false; } - if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) { + if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && + controller->max_activation_depth() > 0) { std::string str = "+"; str += controller->name(); std::string path = controller->path(); @@ -433,8 +441,12 @@ static bool WriteRcFile(const std::map& descripto CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name, const std::string& path, mode_t mode, const std::string& uid, - const std::string& gid, uint32_t flags = 0) - : controller_(version, flags, name, path), mode_(mode), uid_(uid), gid_(gid) {} + const std::string& gid, uint32_t flags, + uint32_t max_activation_depth) + : controller_(version, flags, name, path, max_activation_depth), + mode_(mode), + uid_(uid), + gid_(gid) {} void CgroupDescriptor::set_mounted(bool mounted) { uint32_t flags = controller_.flags(); @@ -502,8 +514,11 @@ static bool CreateV2SubHierarchy( for (const auto& [name, descriptor] : descriptors) { const format::CgroupController* controller = descriptor.controller(); std::uint32_t flags = controller->flags(); + std::uint32_t max_activation_depth = controller->max_activation_depth(); + const int depth = util::GetCgroupDepth(controller->path(), path); + if (controller->version() == 2 && name != CGROUPV2_HIERARCHY_NAME && - flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) { + flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) { std::string str("+"); str += controller->name(); if (!android::base::WriteStringToFile(str, path + "/cgroup.subtree_control")) { diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp new file mode 100644 index 000000000..4a940b774 --- /dev/null +++ b/libprocessgroup/util/Android.bp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2019 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. +// + +package { + default_team: "trendy_team_android_kernel", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_headers { + name: "libprocessgroup_util", + vendor_available: true, + product_available: true, + ramdisk_available: true, + vendor_ramdisk_available: true, + recovery_available: true, + host_supported: true, + native_bridge_supported: true, + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + min_sdk_version: "30", + export_include_dirs: [ + "include", + ], + defaults: ["libprocessgroup_build_flags_cc"], +} + +cc_test { + name: "libprocessgroup_util_test", + header_libs: ["libprocessgroup_util"], + srcs: ["tests/util.cpp"], + test_suites: ["general-tests"], +} diff --git a/libprocessgroup/util/OWNERS b/libprocessgroup/util/OWNERS new file mode 100644 index 000000000..54ea4001e --- /dev/null +++ b/libprocessgroup/util/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 1293033 +surenb@google.com +tjmercier@google.com diff --git a/libprocessgroup/util/TEST_MAPPING b/libprocessgroup/util/TEST_MAPPING new file mode 100644 index 000000000..6ae2658ce --- /dev/null +++ b/libprocessgroup/util/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "libprocessgroup_util_test" + } + ] +} \ No newline at end of file diff --git a/libprocessgroup/util/include/processgroup/util.h b/libprocessgroup/util/include/processgroup/util.h new file mode 100644 index 000000000..5240744c6 --- /dev/null +++ b/libprocessgroup/util/include/processgroup/util.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 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 +#include +#include + +namespace util { + +namespace internal { + +const char SEP = '/'; + +std::string DeduplicateAndTrimSeparators(const std::string& path) { + bool lastWasSep = false; + std::string ret; + + std::copy_if(path.begin(), path.end(), std::back_inserter(ret), [&lastWasSep](char c) { + if (lastWasSep) { + if (c == SEP) return false; + lastWasSep = false; + } else if (c == SEP) { + lastWasSep = true; + } + return true; + }); + + if (ret.length() > 1 && ret.back() == SEP) ret.pop_back(); + + return ret; +} + +} // namespace internal + +unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) { + const std::string deduped_root = internal::DeduplicateAndTrimSeparators(controller_root); + const std::string deduped_path = internal::DeduplicateAndTrimSeparators(cgroup_path); + + if (deduped_root.empty() || deduped_path.empty() || !deduped_path.starts_with(deduped_root)) + return 0; + + return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(), + internal::SEP); +} + +} // namespace util diff --git a/libprocessgroup/util/tests/util.cpp b/libprocessgroup/util/tests/util.cpp new file mode 100644 index 000000000..1de7d6f3f --- /dev/null +++ b/libprocessgroup/util/tests/util.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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 + +#include "gtest/gtest.h" + +using util::GetCgroupDepth; + +TEST(EmptyInputs, bothEmpty) { + EXPECT_EQ(GetCgroupDepth({}, {}), 0); +} + +TEST(EmptyInputs, rootEmpty) { + EXPECT_EQ(GetCgroupDepth({}, "foo"), 0); +} + +TEST(EmptyInputs, pathEmpty) { + EXPECT_EQ(GetCgroupDepth("foo", {}), 0); +} + +TEST(InvalidInputs, pathNotInRoot) { + EXPECT_EQ(GetCgroupDepth("foo", "bar"), 0); +} + +TEST(InvalidInputs, rootLargerThanPath) { + EXPECT_EQ(GetCgroupDepth("/a/long/path", "/short"), 0); +} + +TEST(InvalidInputs, pathLargerThanRoot) { + EXPECT_EQ(GetCgroupDepth("/short", "/a/long/path"), 0); +} + +TEST(InvalidInputs, missingSeparator) { + EXPECT_EQ(GetCgroupDepth("/controller/root", "/controller/rootcgroup"), 0); +} + +TEST(ExtraSeparators, root) { + EXPECT_EQ(GetCgroupDepth("///sys/fs/cgroup", "/sys/fs/cgroup/a/b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys///fs/cgroup", "/sys/fs/cgroup/a/b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs///cgroup", "/sys/fs/cgroup/a/b/c"), 3); + + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "///sys/fs/cgroup/a/b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys///fs/cgroup/a/b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs///cgroup/a/b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup///a/b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a///b/c"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b///c"), 3); +} + +TEST(SeparatorEndings, rootEndsInSeparator) { + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b"), 2); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup///", "/sys/fs/cgroup/a/b"), 2); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b/"), 2); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup///", "/sys/fs/cgroup/a/b/"), 2); +} + +TEST(SeparatorEndings, pathEndsInSeparator) { + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b/"), 2); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b///"), 2); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b/"), 2); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/a/b///"), 2); +} + +TEST(ValidInputs, rootHasZeroDepth) { + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup"), 0); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup"), 0); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/"), 0); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup/", "/sys/fs/cgroup/"), 0); +} + +TEST(ValidInputs, atLeastDepth10) { + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/a/b/c/d/e/f/g/h/i/j"), 10); +} + +TEST(ValidInputs, androidCgroupNames) { + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/system/uid_0/pid_1000"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/uid_0/pid_1000"), 2); + + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/apps/uid_100000/pid_1000"), 3); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/uid_100000/pid_1000"), 2); + + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/apps"), 1); + EXPECT_EQ(GetCgroupDepth("/sys/fs/cgroup", "/sys/fs/cgroup/system"), 1); +} + +TEST(ValidInputs, androidCgroupNames_nonDefaultRoot) { + EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/system/uid_0/pid_1000"), 3); + EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/uid_0/pid_1000"), 2); + + EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/apps/uid_100000/pid_1000"), 3); + EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/uid_100000/pid_1000"), 2); + + EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/apps"), 1); + EXPECT_EQ(GetCgroupDepth("/custom/root", "/custom/root/system"), 1); +}