Skip to content

cgroup,systemd: allow empty slice in cgroupsPath#1812

Merged
giuseppe merged 1 commit intocontainers:mainfrom
kolyshkin:def-slice
Jul 8, 2025
Merged

cgroup,systemd: allow empty slice in cgroupsPath#1812
giuseppe merged 1 commit intocontainers:mainfrom
kolyshkin:def-slice

Conversation

@kolyshkin
Copy link
Copy Markdown
Collaborator

@kolyshkin kolyshkin commented Jul 2, 2025

While it may not be properly documented in runtime-spec, runc's systemd cgroup driver follows some rules when constructing the slice and scope from the linux.cgroupsPath (see 1).

One such rule is, when the slice is empty, it defaults to "system.slice", unless we have cgroup v2 and a rootless container, in which case it defaults to "user.slice". This is supported by runc and although it might be questionable, it makes sense for crun to be compatible.

Add a test case.

Fixes: #1811.

Summary by Sourcery

Allow cgroup systemd driver to fall back to system.slice or user.slice for empty slices in cgroupsPath, aligning crun with runc behavior and ensuring compatibility.

New Features:

  • Default to system.slice (or user.slice for rootless cgroup v2) when the cgroupsPath slice component is empty

Enhancements:

  • Extended get_systemd_scope_and_slice to accept a user_slice flag and detect rootless cgroup v2 for slice assignment

Tests:

  • Added test_systemd_cgroups_path_def_slice to verify default slice selection on systemd cgroup manager handles empty slice

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jul 2, 2025

Reviewer's Guide

Ensure crun’s systemd cgroup driver defaults the slice to “system.slice” or “user.slice” when the slice component is empty in cgroupsPath, mirroring runc behavior, and add a test to validate this logic.

Class diagram for updated systemd cgroup slice logic

classDiagram
    class libcrun_cgroup_args {
        +const char *id
        +pid_t pid
        +const char *cgroup_path
    }
    class CgroupSystemd {
        +libcrun_cgroup_enter_systemd(args, err)
        +get_systemd_scope_and_slice(id, user_slice, cgroup_path, scope, slice)
    }
    libcrun_cgroup_args <.. CgroupSystemd : uses

    CgroupSystemd : +get_systemd_scope_and_slice now takes bool user_slice
    CgroupSystemd : +get_systemd_scope_and_slice sets slice to "system.slice" or "user.slice" if empty
    CgroupSystemd : +libcrun_cgroup_enter_systemd determines rootless for cgroup v2
Loading

File-Level Changes

Change Details Files
Implement default slice assignment in systemd cgroup scope handling
  • Extend get_systemd_scope_and_slice signature to accept a user_slice flag
  • After extracting slice name, detect empty string and set it to “system.slice” or “user.slice” based on user_slice
  • In libcrun_cgroup_enter_systemd, detect rootless mode for cgroup v2 and supply that flag to the scope-and-slice logic
src/libcrun/cgroup-systemd.c
Add test for default slice behavior when cgroupsPath omits slice
  • Add test_systemd_cgroups_path_def_slice in tests/test_start.py with feature and platform checks
  • Build a container with cgroupsPath ":crun:123" and determine expected slice (system/user)
  • Use systemctl (and --user fallback) to verify the slice, clean up container on exit
  • Register the new test case in the all_tests mapping
tests/test_start.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @kolyshkin - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/libcrun/cgroup-systemd.c:140` </location>
<code_context>
         *n = '\0';
+
+      /* Ref: https://github.com/opencontainers/runc/blob/main/docs/systemd.md#systemd-unit-name-and-placement */
+      if (is_empty_string (*slice))
+        {
+          if (user_slice)
+            *slice = xstrdup ("user.slice");
+          else
+            *slice = xstrdup ("system.slice");
+        }
     }
</code_context>

<issue_to_address>
Potential memory leak if *slice was previously allocated.

Free the previous value of *slice before assigning a new value with xstrdup to prevent memory leaks.
</issue_to_address>

### Comment 2
<location> `tests/test_start.py:540` </location>
<code_context>
+
+    conf = base_config()
+    add_all_namespaces(conf)
+    conf['linux']['cgroupsPath'] = ':crun:123' # crun-123.scope
+
+    cid = None
</code_context>

<issue_to_address>
Consider adding a test for a fully empty cgroupsPath.

Also test with cgroupsPath set to an empty string or None to verify correct handling of these edge cases.
</issue_to_address>

### Comment 3
<location> `tests/test_start.py:549` </location>
<code_context>
+        scope = json.loads(state)['systemd-scope']
+
+        want='system.slice'
+        if is_rootless():
+            want='user.slice'
+
</code_context>

<issue_to_address>
Unreachable code or indentation issue.

Please check the indentation and logic to ensure all code paths are reachable and correct.
</issue_to_address>

### Comment 4
<location> `src/libcrun/cgroup-systemd.c:111` </location>
<code_context>

 static void
-get_systemd_scope_and_slice (const char *id, const char *cgroup_path, char **scope, char **slice)
+get_systemd_scope_and_slice (const char *id, bool user_slice, const char *cgroup_path,
+                             char **scope, char **slice)
 {
</code_context>

<issue_to_address>
Consider refactoring get_systemd_scope_and_slice to accept a default_slice string instead of a user_slice boolean, and move rootless logic to the caller.

```c
// 1) Change get_systemd_scope_and_slice signature: drop `user_slice` and add
//    const char *default_slice`
-static void get_systemd_scope_and_slice(
-    const char *id,
-    bool user_slice,
-    const char *cgroup_path,
-    char **scope,
-    char **slice)
+static void get_systemd_scope_and_slice(
+    const char *id,
+    const char *cgroup_path,
+    const char *default_slice,
+    char **scope,
+    char **slice)
 {
     char *n;

     if (!cgroup_path || !*cgroup_path) {
         xasprintf(scope, "crun-%s.scope", id);
         return;
     }

     // ... existing scope logic ...

     if (slice) {
         *slice = xstrdup(cgroup_path);
         n = strchr(*slice, ':');
         if (n) *n = '\0';

         // simplified default‐slice logic
         if (is_empty_string(*slice))
             *slice = xstrdup(default_slice);
     }
 }
```

```c
// 2) In the caller, compute default_slice once
int rootless = 0;
if (cgroup_mode == CGROUP_MODE_UNIFIED) {
    rootless = is_rootless(err);
    if (rootless < 0)
        return rootless;
}

const char *default_slice = rootless
    ? "user.slice"
    : "system.slice";

get_systemd_scope_and_slice(
    id,
    cgroup_path,
    default_slice,
    &scope,
    &slice);
```

Benefits:
- Eliminates the extra `bool user_slice` flag and inner `if/else`
- Moves all rootless logic into the caller
- Reduces nesting and keeps `get_systemd_scope_and_slice` focused on string mangling only
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/libcrun/cgroup-systemd.c
Comment thread tests/test_start.py Outdated

conf = base_config()
add_all_namespaces(conf)
conf['linux']['cgroupsPath'] = ':crun:123' # crun-123.scope
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a test for a fully empty cgroupsPath.

Also test with cgroupsPath set to an empty string or None to verify correct handling of these edge cases.

Comment thread tests/test_start.py
scope = json.loads(state)['systemd-scope']

want='system.slice'
if is_rootless():
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Unreachable code or indentation issue.

Please check the indentation and logic to ensure all code paths are reachable and correct.

}

static void
get_systemd_scope_and_slice (const char *id, const char *cgroup_path, char **scope, char **slice)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring get_systemd_scope_and_slice to accept a default_slice string instead of a user_slice boolean, and move rootless logic to the caller.

// 1) Change get_systemd_scope_and_slice signature: drop `user_slice` and add
//    const char *default_slice`
-static void get_systemd_scope_and_slice(
-    const char *id,
-    bool user_slice,
-    const char *cgroup_path,
-    char **scope,
-    char **slice)
+static void get_systemd_scope_and_slice(
+    const char *id,
+    const char *cgroup_path,
+    const char *default_slice,
+    char **scope,
+    char **slice)
 {
     char *n;

     if (!cgroup_path || !*cgroup_path) {
         xasprintf(scope, "crun-%s.scope", id);
         return;
     }

     // ... existing scope logic ...

     if (slice) {
         *slice = xstrdup(cgroup_path);
         n = strchr(*slice, ':');
         if (n) *n = '\0';

         // simplified default‐slice logic
         if (is_empty_string(*slice))
             *slice = xstrdup(default_slice);
     }
 }
// 2) In the caller, compute default_slice once
int rootless = 0;
if (cgroup_mode == CGROUP_MODE_UNIFIED) {
    rootless = is_rootless(err);
    if (rootless < 0)
        return rootless;
}

const char *default_slice = rootless
    ? "user.slice"
    : "system.slice";

get_systemd_scope_and_slice(
    id,
    cgroup_path,
    default_slice,
    &scope,
    &slice);

Benefits:

  • Eliminates the extra bool user_slice flag and inner if/else
  • Moves all rootless logic into the caller
  • Reduces nesting and keeps get_systemd_scope_and_slice focused on string mangling only

Comment thread tests/test_start.py
Comment on lines +533 to +535
if 'SYSTEMD' not in get_crun_feature_string():
return 77
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment thread tests/test_start.py
Comment on lines +549 to +552
if is_rootless():
want='user.slice'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment thread tests/test_start.py
Comment on lines +554 to +557
if got != want:
got = subprocess.check_output(['systemctl', '--user', 'show','-PSlice', scope], close_fds=False).decode().strip()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment thread tests/test_start.py
Comment on lines +557 to +561
if got != want:
sys.stderr.write("# Got Slice %s, want %s\n" % got, want)
return 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment thread tests/test_start.py
Comment on lines +564 to +566
if cid is not None:
run_crun_command(["delete", "-f", cid])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment thread tests/test_start.py Outdated
if cid is not None:
run_crun_command(["delete", "-f", cid])

if is_rootless():
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

@packit-as-a-service
Copy link
Copy Markdown

Ephemeral COPR build failed. @containers/packit-build please check.

1 similar comment
@packit-as-a-service
Copy link
Copy Markdown

Ephemeral COPR build failed. @containers/packit-build please check.

While it may not be properly documented in runtime-spec, runc's systemd
cgroup driver follows some rules when constructing the slice and scope
from the linux.cgroupsPath (see [1]).

One such rule is, when the slice is empty, it defaults to "system.slice",
unless we have cgroup v2 and a rootless container, in which case it
defaults to "user.slice". This is supported by runc and although it
might be questionable, it makes sense for crun to be compatible.

Add a test case.

Fixes: 1811.

[1]: https://github.com/opencontainers/runc/blob/main/docs/systemd.md#systemd-unit-name-and-placement

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
@packit-as-a-service
Copy link
Copy Markdown

TMT tests failed. @containers/packit-build please check.

Copy link
Copy Markdown
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@giuseppe giuseppe merged commit e8b3952 into containers:main Jul 8, 2025
40 of 43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default systemd slice is empty

2 participants