diff options
-rw-r--r-- | sd_login/.gitignore | 1 | ||||
-rw-r--r-- | sd_login/Makefile | 4 | ||||
-rw-r--r-- | sd_login/systemd_cgroup.go | 16 | ||||
-rw-r--r-- | sd_login/systemd_cgroup_generic.go | 163 | ||||
-rw-r--r-- | sd_login/systemd_cgroup_skip.go | 102 | ||||
-rwxr-xr-x | sd_login/systemd_cgroup_skip_gen.go.gen | 41 | ||||
-rw-r--r-- | sd_login/systemd_cgroup_systemd.go | 105 |
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() +} |