This is an automated email from the ASF dual-hosted git repository.

bmahler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git

commit 2241e64caf7ecd735d9a8aaabf02c3f5f3a35ab8
Author: Jason Zhou <[email protected]>
AuthorDate: Fri Jul 26 16:53:34 2024 -0400

    [cgroups2] Add helper to normalize allow/deny list.
    
    This patch adds a public helper function to abstract away the logic used
    to make a list comply with the 'normalized' requirements.
    
    As a reminder, an allow or deny list is 'normalized' iff everything below
    are true:
    
    1. No Entry has empty accesses specified.
    2. No two entries on the same list can have the same selector (type,
       major & minor numbers).
    3. No two entries on the same list can be encompassed by the other
       entry (see Entry::encompasses).
    
    Review: https://reviews.apache.org/r/75104/
---
 src/linux/cgroups2.cpp                     | 59 +++++++++++++++++++++++++++
 src/linux/cgroups2.hpp                     |  5 +++
 src/tests/containerizer/cgroups2_tests.cpp | 65 ++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+)

diff --git a/src/linux/cgroups2.cpp b/src/linux/cgroups2.cpp
index bd6018050..5b027c5fb 100644
--- a/src/linux/cgroups2.cpp
+++ b/src/linux/cgroups2.cpp
@@ -30,6 +30,7 @@
 #include <process/pid.hpp>
 
 #include <stout/adaptor.hpp>
+#include <stout/linkedhashmap.hpp>
 #include <stout/none.hpp>
 #include <stout/numify.hpp>
 #include <stout/os.hpp>
@@ -1530,6 +1531,64 @@ bool normalized(const vector<Entry>& query)
   return true;
 }
 
+
+vector<Entry> normalize(const vector<Entry>& to_normalize)
+{
+  auto strip_empties = [](const vector<Entry>& entries) {
+    vector<Entry> stripped = {};
+    foreach (const Entry& entry, entries) {
+      if (!entry.access.none()) {
+        stripped.push_back(entry);
+      }
+    }
+    return stripped;
+  };
+
+  auto deduplicate = [](const vector<Entry>& entries) {
+    LinkedHashMap<string, Entry> deduplicated;
+    foreach (const Entry& entry, entries) {
+      if (!deduplicated.contains(stringify(entry.selector))) {
+        deduplicated[stringify(entry.selector)] = entry;
+      }
+
+      Entry& e = deduplicated.at(stringify(entry.selector));
+      e.access.write |= entry.access.write;
+      e.access.read |= entry.access.read;
+      e.access.mknod |= entry.access.mknod;
+    }
+
+    return deduplicated.values();
+  };
+
+  auto strip_encompassed = [](const vector<Entry>& entries) {
+    vector<Entry> result = {};
+    foreach (const Entry& entry, entries) {
+      bool is_encompassed = [&]() {
+        foreach (const Entry& other, entries) {
+          if (!cgroups::devices::operator==(entry.selector, other.selector)
+              && other.encompasses(entry)) {
+            return true;
+          }
+        }
+        return false;
+      }();
+
+      // Skip entries that are encompassed by other entries.
+      if (!is_encompassed) {
+        result.push_back(entry);
+      }
+    }
+    return result;
+  };
+
+  vector<Entry> result = to_normalize;
+  result = strip_empties(result);
+  result = deduplicate(result);
+  result = strip_encompassed(result);
+  CHECK(normalized(result));
+  return result;
+}
+
 } // namespace devices {
 
 } // namespace cgroups2 {
diff --git a/src/linux/cgroups2.hpp b/src/linux/cgroups2.hpp
index ef4e67932..e13e57076 100644
--- a/src/linux/cgroups2.hpp
+++ b/src/linux/cgroups2.hpp
@@ -451,6 +451,11 @@ Try<Nothing> configure(
 //   entry (see Entry::encompasses).
 bool normalized(const std::vector<Entry>& entries);
 
+
+// Modifies the given entries such that the three normalization requirements
+// are fulfilled.
+std::vector<Entry> normalize(const std::vector<Entry>& entries);
+
 } // namespace devices {
 
 } // namespace cgroups2 {
diff --git a/src/tests/containerizer/cgroups2_tests.cpp 
b/src/tests/containerizer/cgroups2_tests.cpp
index 388639bed..de31a330a 100644
--- a/src/tests/containerizer/cgroups2_tests.cpp
+++ b/src/tests/containerizer/cgroups2_tests.cpp
@@ -907,6 +907,71 @@ TEST(Cgroups2DevicesTest, NormalizedTest)
   }));
 }
 
+
+TEST(Cgroups2DevicesTest, NormalizeTest)
+{
+  // Empty entries are eliminated.
+  devices::Entry empty_entry;
+  empty_entry.selector.type = devices::Entry::Selector::Type::CHARACTER;
+  empty_entry.selector.major = 1;
+  empty_entry.selector.minor = 3;
+  empty_entry.access.read = false;
+  empty_entry.access.write = false;
+  empty_entry.access.mknod = false;
+  EXPECT_EQ(
+      vector<devices::Entry>{},
+      cgroups2::devices::normalize({empty_entry}));
+
+  // Entries with matching type, major & minor numbers are combined into one
+  EXPECT_EQ(
+      vector<devices::Entry>{
+        CHECK_NOTERROR(devices::Entry::parse("b 3:1 rwm"))
+      },
+      cgroups2::devices::normalize({
+        CHECK_NOTERROR(devices::Entry::parse("b 3:1 rw")),
+        CHECK_NOTERROR(devices::Entry::parse("b 3:1 m"))
+      }));
+
+  // Entries that are encompassed by another are eliminated
+  EXPECT_EQ(
+      vector<devices::Entry>{CHECK_NOTERROR(devices::Entry::parse("c *:* 
rw"))},
+      cgroups2::devices::normalize({
+        CHECK_NOTERROR(devices::Entry::parse("c *:* rw")),
+        CHECK_NOTERROR(devices::Entry::parse("c 3:1 w"))
+      }));
+
+  // Test all three scenarios
+  EXPECT_EQ(
+      vector<devices::Entry>{CHECK_NOTERROR(devices::Entry::parse("c *:* 
rw"))},
+      cgroups2::devices::normalize({
+        empty_entry,
+        CHECK_NOTERROR(devices::Entry::parse("c *:* r")),
+        CHECK_NOTERROR(devices::Entry::parse("c *:* w")),
+        CHECK_NOTERROR(devices::Entry::parse("c 3:1 w"))
+       }));
+
+  // Normalized lists are unaffected
+  vector<devices::Entry> already_normalized = {
+    CHECK_NOTERROR(devices::Entry::parse("c *:* m")),
+    CHECK_NOTERROR(devices::Entry::parse("b *:* m")),
+    CHECK_NOTERROR(devices::Entry::parse("c 5:1 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 4:0 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 4:1 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 136:* rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 5:2 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 10:200 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 1:3 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 1:5 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 1:7 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 5:0 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 1:9 rwm")),
+    CHECK_NOTERROR(devices::Entry::parse("c 1:8 rwm"))
+  };
+  EXPECT_TRUE(cgroups2::devices::normalized(already_normalized));
+  EXPECT_EQ(already_normalized,
+            cgroups2::devices::normalize(already_normalized));
+}
+
 } // namespace tests {
 
 } // namespace internal {

Reply via email to