File: util.go

package info (click to toggle)
singularity-container 4.1.5%2Bds4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 43,876 kB
  • sloc: asm: 14,840; sh: 3,190; ansic: 1,751; awk: 414; makefile: 413; python: 99
file content (175 lines) | stat: -rw-r--r-- 5,036 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (c) 2022-2024, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.

package cgroups

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/opencontainers/runc/libcontainer/cgroups"
	lccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/sylabs/singularity/v4/internal/pkg/util/rootless"
	"github.com/sylabs/singularity/v4/pkg/sylog"
	"golang.org/x/sys/unix"
)

const unifiedMountPoint = "/sys/fs/cgroup"

// pidToPath returns the path of the cgroup containing process ID pid.
// It is assumed that for v1 cgroups the devices controller is in use.
func pidToPath(pid int) (path string, err error) {
	if pid == 0 {
		return "", fmt.Errorf("must provide a valid pid")
	}

	pidCGFile := fmt.Sprintf("/proc/%d/cgroup", pid)
	paths, err := lccgroups.ParseCgroupFile(pidCGFile)
	if err != nil {
		return "", fmt.Errorf("cannot read %s: %w", pidCGFile, err)
	}

	// cgroups v2 path is always given by the unified "" subsystem
	ok := false
	if lccgroups.IsCgroup2UnifiedMode() {
		path, ok := paths[""]
		if !ok {
			return "", fmt.Errorf("could not find cgroups v2 unified path")
		}
		return path, nil
	}

	// For cgroups v1 we are relying on fetching the 'devices' subsystem path.
	// The devices subsystem is needed for our OCI engine and its presence is
	// enforced in runc/libcontainer/cgroups/fs initialization without 'skipDevices'.
	// This means we never explicitly put a container into a cgroup without a
	// set 'devices' path.
	path, ok = paths["devices"]
	if !ok {
		return "", fmt.Errorf("could not find cgroups v1 path (using devices subsystem)")
	}
	return path, nil
}

// DefaultPathForPid returns a default cgroup path for a given PID.
func DefaultPathForPid(systemd bool, pid int) (group string) {
	// Default naming is pid of first process added to cgroup.
	strPid := strconv.Itoa(pid)
	// Request is for an empty cgroup... name it for the requestor's (our) pid.
	if pid == -1 {
		strPid = "parent-" + strconv.Itoa(os.Getpid())
	}

	if systemd {
		if os.Getuid() == 0 {
			group = "system.slice:singularity:" + strPid
		} else {
			group = "user.slice:singularity:" + strPid
		}
	} else {
		group = filepath.Join("/singularity", strPid)
	}
	return group
}

// HasDbus checks if DBUS_SESSION_BUS_ADDRESS is set, and sane.
// Logs unset var / non-existent target at DEBUG level.
func HasDbus() (bool, error) {
	dbusEnv := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
	if dbusEnv == "" {
		return false, fmt.Errorf("DBUS_SESSION_BUS_ADDRESS is not set")
	}

	if !strings.HasPrefix(dbusEnv, "unix:") {
		return false, fmt.Errorf("DBUS_SESSION_BUS_ADDRESS %q is not a 'unix:' socket", dbusEnv)
	}

	return true, nil
}

// HasXDGRuntimeDir checks if XDG_Runtime_Dir is set, and sane.
// Logs unset var / non-existent target at DEBUG level.
func HasXDGRuntimeDir() (bool, error) {
	xdgRuntimeEnv := os.Getenv("XDG_RUNTIME_DIR")
	if xdgRuntimeEnv == "" {
		return false, fmt.Errorf("XDG_RUNTIME_DIR is not set")
	}

	fi, err := os.Stat(xdgRuntimeEnv)
	if err != nil {
		return false, fmt.Errorf("XDG_RUNTIME_DIR %q not accessible: %v", xdgRuntimeEnv, err)
	}

	if !fi.IsDir() {
		return false, fmt.Errorf("XDG_RUNTIME_DIR %q is not a directory", xdgRuntimeEnv)
	}

	if err := unix.Access(xdgRuntimeEnv, unix.W_OK); err != nil {
		return false, fmt.Errorf("XDG_RUNTIME_DIR %q is not writable", xdgRuntimeEnv)
	}

	return true, nil
}

// CanUseCgroups checks whether it's possible to use the cgroups manager.
// - Host root can always use cgroups.
// - Rootless needs cgroups v2.
// - Rootless needs systemd manager.
// - Rootless needs DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR set properly.
// warn controls whether configuration problems preventing use of cgroups will be logged as warnings, or debug messages.
func CanUseCgroups(systemd bool, warn bool) bool {
	uid, err := rootless.Getuid()
	if err != nil {
		sylog.Errorf("cannot determine uid: %v", err)
		return false
	}

	if uid == 0 {
		return true
	}

	rootlessOK := true

	if !cgroups.IsCgroup2UnifiedMode() {
		rootlessOK = false
		if warn {
			sylog.Warningf("Rootless cgroups require the system to be configured for cgroups v2 in unified mode.")
		} else {
			sylog.Debugf("Rootless cgroups require 'systemd cgroups' to be enabled in singularity.conf")
		}
	}

	if !systemd {
		rootlessOK = false
		if warn {
			sylog.Warningf("Rootless cgroups require 'systemd cgroups' to be enabled in singularity.conf")
		} else {
			sylog.Debugf("Rootless cgroups require 'systemd cgroups' to be enabled in singularity.conf")
		}
	}

	if ok, err := HasXDGRuntimeDir(); !ok {
		rootlessOK = false
		if warn {
			sylog.Warningf("%s", err)
		} else {
			sylog.Debugf("%s", err)
		}
	}

	if ok, err := HasDbus(); !ok {
		rootlessOK = false
		if warn {
			sylog.Warningf("%s", err)
		} else {
			sylog.Debugf("%s", err)
		}
	}

	return rootlessOK
}