summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sd_login/.gitignore1
-rw-r--r--sd_login/Makefile4
-rw-r--r--sd_login/systemd_cgroup.go16
-rw-r--r--sd_login/systemd_cgroup_generic.go163
-rw-r--r--sd_login/systemd_cgroup_skip.go102
-rwxr-xr-xsd_login/systemd_cgroup_skip_gen.go.gen41
-rw-r--r--sd_login/systemd_cgroup_systemd.go105
7 files changed, 415 insertions, 17 deletions
diff --git a/sd_login/.gitignore b/sd_login/.gitignore
index e94b335..5ad2264 100644
--- a/sd_login/.gitignore
+++ b/sd_login/.gitignore
@@ -1 +1,2 @@
/systemd_process.go
+/systemd_cgroup_skip_gen.go
diff --git a/sd_login/Makefile b/sd_login/Makefile
index bed4104..763ab3c 100644
--- a/sd_login/Makefile
+++ b/sd_login/Makefile
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-files.src.gen += systemd_process.go
+files.src.gen += systemd_process.go systemd_cgroup_skip_gen.go
files.generate: $(files.src.gen)
maintainer-clean:
@@ -22,4 +22,6 @@ maintainer-clean:
%.go: %.go.gen
./$^ > $@
+systemd_cgroup_skip_gen.go: systemd_cgroup_skip.go
+
.DELETE_ON_ERROR:
diff --git a/sd_login/systemd_cgroup.go b/sd_login/systemd_cgroup.go
deleted file mode 100644
index 12d5ade..0000000
--- a/sd_login/systemd_cgroup.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package sd_login
-
-type _Cgroup interface {
- MustSkipSystemPrefix() _Cgroup
- GetSession() SessionName
- GetOwnerUser() UserID
- GetMachine() MachineName
-
- GetUserSlice() string
- GetUserUnit() string
-
- GetSlice() string
- GetUnit() string
-}
-
-func (pid ProcessID) getCgroup() (_Cgroup, error)
diff --git a/sd_login/systemd_cgroup_generic.go b/sd_login/systemd_cgroup_generic.go
new file mode 100644
index 0000000..86757d1
--- /dev/null
+++ b/sd_login/systemd_cgroup_generic.go
@@ -0,0 +1,163 @@
+// Copyright (C) 2016-2017 Luke Shumaker <lukeshu@sbcglobal.net>
+//
+// 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 sd_login
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "golang.org/x/sys/unix"
+)
+
+type _Cgroup string
+
+var cgVersion_cache uint
+
+func cgVersion() uint {
+ if cgVersion_cache == 0 {
+ fs, err := statfs("/sys/fs/cgroup/")
+ if err != nil {
+ return 0
+ }
+ if fs.Type == magic_CGROUP2_SUPER {
+ cgVersion_cache = 2
+ } else if fs.Type == magic_TMPFS {
+ // XXX: systemd-specific cgroup v1 logic
+ fs, err = statfs("/sys/fs/cgroup/systemd/")
+ if err != nil {
+ return 0
+ }
+ if fs.Type == magic_CGROUP2_SUPER {
+ cgVersion_cache = 2
+ } else {
+ cgVersion_cache = 1
+ }
+ }
+ }
+ return cgVersion_cache
+}
+
+// getCgroup returns the cgroup path of the process, relative to the
+// root of the cgroup hierarchy.
+//
+// In cgroup v2, there is a only one cgroup hierarchy, so the behavior
+// is obvious.
+//
+// However, in cgroup v1, there were multiple cgroup hierarchies, so
+// this function must decide which hierarchy to use. We chooses the
+// first hierarchy with either the "name=systemd" controller or the
+// "name=elogind" controller attached to it[1]. If no such hierarchy
+// exists, then an error is returned.
+//
+// However, it is possible to generally use cgroup v1, but use a
+// single (named) v2 hierarchy alongside many v1 hierarchies. In this
+// case, we use the v2 hierarchy iff it is named "systemd", otherwise
+// we use the cgroup v1 behavior.
+//
+// [1]: The "first" in that sentence is worrying; shouldn't the choice
+// of hierarchy not depend on the undefined order that controllers are
+// listed in? Well, a controller may be attached to only one
+// hierarchy at a time. So there is only an ambiguity for "first" to
+// come in if both "name=systemd" and "name=elogind" controllers
+// exist. Systemd and elogind cannot be used together, so this isn't
+// a concern.
+//
+// BUG(lukeshu): ProcessID.getCgroup: Has systemd-specific logic. However,
+// it is only for "legacy" cgroup v1 compatibility; the cgroup v2
+// logic is totally implementation-agnostic. Unfortunately(?), no
+// distro seems to be using cgroup v2 (introduced in Linux 4.5) yet by
+// default.
+func (pid ProcessID) getCgroup() (_Cgroup, error) {
+ cgVer := cgVersion()
+
+ var cgroupFilename string
+ if pid == 0 {
+ cgroupFilename = "/proc/self/cgroup"
+ } else {
+ cgroupFilename = fmt.Sprintf("/proc/%d/cgroup", pid)
+ }
+
+ f, err := os.Open(cgroupFilename)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ bf := bufio.NewReader(f)
+
+ for {
+ line, err := bf.ReadString('\n')
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return "", err
+ }
+ line = strings.TrimSuffix(line, "\n")
+
+ parts := strings.SplitN(line, ":", 3)
+ if len(parts) != 3 {
+ continue
+ }
+
+ hierarchy := parts[0]
+ controllers := parts[1]
+ path := _Cgroup(parts[2])
+
+ switch cgVer {
+ case 1:
+ for _, controller := range strings.Split(controllers, ",") {
+ if controller == "name=systemd" || controller == "name=elogind" {
+ return path, nil
+ }
+ }
+ case 2:
+ if hierarchy != "0" {
+ continue
+ }
+ return path, nil
+ }
+ }
+ return "", unix.ENODATA
+}
+
+// cgGetRootPath determines the cgroup that all other cgroups belong
+// to. The common case is just "/", but it could be something else if
+// we are inside of a container, but have a view of the entier cgroup
+// hierarchy.
+//
+// BUG(lukeshu): cgGetRootPath: works correctly on systemd and
+// elogind, but I'm not sure it's general.
+func cgGetRootPath() (_Cgroup, error) {
+ cgpath, err := ProcessID(1).getCgroup()
+ if err != nil {
+ return "/", err
+ }
+
+ cgpath = _Cgroup(trimOneSuffix(string(cgpath),
+ "/init.scope", // modern systemd
+ "/system.slice", // legacy systemd
+ "/system", // even more legacy systemd
+ ))
+
+ return cgpath, nil
+}
+
+func cgUnescape(s string) string {
+ return strings.TrimPrefix(s, "_")
+}
diff --git a/sd_login/systemd_cgroup_skip.go b/sd_login/systemd_cgroup_skip.go
new file mode 100644
index 0000000..e6e9042
--- /dev/null
+++ b/sd_login/systemd_cgroup_skip.go
@@ -0,0 +1,102 @@
+// Copyright (C) 2016-2017 Luke Shumaker <lukeshu@sbcglobal.net>
+//
+// 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 sd_login
+
+// The "skip:" comments in this file are used to automagically
+// generate helper functions. The lack of a space between "//" and
+// "skip:" is important
+
+import (
+ "strconv"
+ "strings"
+)
+
+func (cgroup _Cgroup) SkipPath(prefix _Cgroup) (_Cgroup, bool) {
+ //skip: SkipPath(prefix _Cgroup) : SkipPath(prefix)
+ rest, ok := path_startswith(string(cgroup), string(prefix))
+ if ok {
+ return _Cgroup(rest), true
+ } else {
+ return cgroup, false
+ }
+}
+
+// Skip (*.slice){1,}
+func (cgroup _Cgroup) SkipSlices() (_Cgroup, bool) {
+ //skip: SkipSlices() : SkipSlices()
+ cg := string(cgroup)
+ skipped := false
+ for {
+ cg = strings.TrimLeft(cg, "/")
+ part, rest := split2(cg, '/')
+ if !valid_slice_name(part) {
+ return _Cgroup(cg), skipped
+ }
+ skipped = true
+ cg = rest
+ }
+}
+
+// Skip user@*.service
+func (cgroup _Cgroup) SkipUserManager() (_Cgroup, bool) {
+ //skip: SkipUserManager() : SkipUserManager()
+ part, rest := split2(strings.TrimLeft(string(cgroup), "/"), '/')
+ uid_str, ok := trimPrefixSuffix(part, "user@", ".service")
+ if !ok {
+ return cgroup, false
+ }
+ _, err := strconv.Atoi(uid_str)
+ if err != nil {
+ return cgroup, false
+ }
+ return _Cgroup(rest), true
+}
+
+// Skip session-*.scope
+func (cgroup _Cgroup) SkipSession() (_Cgroup, bool) {
+ //skip: SkipSession() : SkipSession()
+ part, rest := split2(strings.TrimLeft(string(cgroup), "/"), '/')
+ session, ok := trimPrefixSuffix(part, "session-", ".scope")
+ if !ok {
+ return cgroup, false
+ }
+ if !valid_session_name(session) {
+ return cgroup, false
+ }
+ return _Cgroup(rest), true
+}
+
+// Skip (/*.slice){0,}/(user@*.service|session-*.scope)
+func (cgroup _Cgroup) SkipUserPrefix() (_Cgroup, bool) {
+ //skip: SkipUserPrefix() : SkipUserPrefix()
+ cgroup, _ = cgroup.SkipSlices()
+ cgroup, ok := cgroup.SkipUserManager()
+ if ok {
+ return cgroup, ok
+ }
+ return cgroup.SkipSession()
+}
+
+// Skip cgGetRootPath
+func (cgroup _Cgroup) SkipSystemPrefix() (_Cgroup, bool) {
+ //skip: SkipSystemPrefix() : SkipSystemPrefix()
+
+ rootpath, err := cgGetRootPath()
+ if err != nil {
+ return cgroup, false
+ }
+
+ return cgroup.SkipPath(rootpath)
+}
diff --git a/sd_login/systemd_cgroup_skip_gen.go.gen b/sd_login/systemd_cgroup_skip_gen.go.gen
new file mode 100755
index 0000000..26c515a
--- /dev/null
+++ b/sd_login/systemd_cgroup_skip_gen.go.gen
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+# Copyright (C) 2017 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# 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.
+
+{
+ printf '//'
+ printf ' %q' "$0" "$@"
+ printf '\n// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n\n'
+
+ echo package sd_login
+
+ grep -o '//skip:.*' "$1" | cut -d: -f2- | while read -r line; do
+ sig=$(echo $(cut -d: -f1 <<<"$line"))
+ cal=$(echo $(cut -d: -f2 <<<"$line"))
+ cat <<EOF
+
+func (cgroup _Cgroup) Maybe${sig} _Cgroup {
+ cgroup, _ = cgroup.${cal}
+ return cgroup
+}
+func (cgroup _Cgroup) Must${sig} _Cgroup {
+ cgroup, ok := cgroup.${cal}
+ if !ok {
+ return ""
+ }
+ return cgroup
+}
+EOF
+ done
+} | gofmt
diff --git a/sd_login/systemd_cgroup_systemd.go b/sd_login/systemd_cgroup_systemd.go
new file mode 100644
index 0000000..834d378
--- /dev/null
+++ b/sd_login/systemd_cgroup_systemd.go
@@ -0,0 +1,105 @@
+// Copyright (C) 2016-2017 Luke Shumaker <lukeshu@sbcglobal.net>
+//
+// 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 sd_login
+
+import (
+ "os"
+ "strconv"
+ "strings"
+)
+
+// XXX: logind
+func (cgroup _Cgroup) GetSession() SessionName {
+ unit := cgroup.GetUnit()
+
+ session, ok := trimPrefixSuffix(unit, "session-", ".scope")
+ if !ok || !valid_session_name(session) {
+ return ""
+ }
+
+ return SessionName(session)
+}
+
+func (cgroup _Cgroup) GetOwnerUser() UserID {
+ slice := cgroup.GetSlice()
+
+ uid_str, ok := trimPrefixSuffix(slice, "user-", ".slice")
+ if !ok {
+ return -1
+ }
+
+ uid, err := strconv.Atoi(uid_str)
+ if err != nil {
+ return -1
+ }
+
+ return UserID(uid)
+}
+
+// XXX: machined
+func (cgroup _Cgroup) GetMachine() MachineName {
+ unit := cgroup.GetUnit()
+ if unit == "" {
+ return ""
+ }
+
+ machine, err := os.Readlink("/run/systemd/machines/unit:" + unit)
+ if err != nil {
+ return ""
+ }
+ return MachineName(machine)
+}
+
+// XXX: systemd
+func (cgroup _Cgroup) decodeUnit() string {
+ unit, _ := split2(string(cgroup), '/')
+ if len(unit) < 3 {
+ return ""
+ }
+ unit = cgUnescape(unit)
+ if valid_unit_name(unit)&(unit_name_plain|unit_name_instance) == 0 {
+ return ""
+ }
+ return unit
+}
+
+func (cgroup _Cgroup) GetUnit() string {
+ unit := cgroup.MaybeSkipSlices().decodeUnit()
+ if strings.HasSuffix(unit, ".slice") {
+ return ""
+ }
+ return unit
+}
+func (cgroup _Cgroup) GetUserUnit() string {
+ return cgroup.MustSkipUserPrefix().GetUnit()
+}
+func (cgroup _Cgroup) GetSlice() string {
+ cg := string(cgroup)
+ n := 0
+ for {
+ cg = strings.TrimLeft(cg, "/")
+ part, rest := split2(cg, '/')
+ if !valid_slice_name(part) {
+ if n == 0 {
+ return "-.slice"
+ }
+ return _Cgroup(cg).decodeUnit()
+ }
+ cg = rest
+ }
+}
+func (cgroup _Cgroup) GetUserSlice() string {
+ return cgroup.MustSkipUserPrefix().GetSlice()
+}