From 131b228ee70abffea7ca8580af7b5efe2cd36ead Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 16 Apr 2017 01:27:45 -0400 Subject: sd_login: work on it --- readme.go | 2 +- sd_login/.gitignore | 1 - sd_login/Makefile | 3 - sd_login/cgroup.go | 16 ++++ sd_login/cgroup_generic.go | 163 ---------------------------------------- sd_login/cgroup_skip.go | 98 ------------------------ sd_login/cgroup_skip_gen.go.gen | 41 ---------- sd_login/cgroup_systemd.go | 107 -------------------------- sd_login/doc.go | 39 ++++++---- sd_login/gen.go | 3 - sd_login/type_machine.go | 55 +++++++++++++- sd_login/type_monitor.go | 12 ++- sd_login/type_pid.go.gen | 17 ++++- sd_login/type_seat.go | 116 ++++++++++++++++++++++++++-- sd_login/type_session.go | 11 ++- sd_login/util.go | 25 +++++- sd_login/util_valid.go | 49 ++++++------ 17 files changed, 283 insertions(+), 475 deletions(-) create mode 100644 sd_login/cgroup.go delete mode 100644 sd_login/cgroup_generic.go delete mode 100644 sd_login/cgroup_skip.go delete mode 100755 sd_login/cgroup_skip_gen.go.gen delete mode 100644 sd_login/cgroup_systemd.go delete mode 100644 sd_login/gen.go diff --git a/readme.go b/readme.go index 269e504..080c367 100644 --- a/readme.go +++ b/readme.go @@ -25,7 +25,7 @@ // sd_event | N/A | Not implemented; not nescessary in Go. // sd_id128 | DONE | Package sd_id128 reads, generates, and processes 128-bit ID values. // sd_journal | TODO | TODO. -// sd_login | TODO | TODO. +// sd_login | WIP | Package sd_login TODO. // sd_messages | DONE | Package sd_messages is a table of constant message IDs understood by journald. // ------------+--------+------------------------------------------------------------------------------- package libsystemd diff --git a/sd_login/.gitignore b/sd_login/.gitignore index 84030d6..8050442 100644 --- a/sd_login/.gitignore +++ b/sd_login/.gitignore @@ -1,2 +1 @@ /type_pid.go -/cgroup_skip_gen.go diff --git a/sd_login/Makefile b/sd_login/Makefile index 055ee05..daf076d 100644 --- a/sd_login/Makefile +++ b/sd_login/Makefile @@ -13,7 +13,6 @@ # limitations under the License. files.src.gen += type_pid.go -files.src.gen += cgroup_skip_gen.go files.generate: $(files.src.gen) maintainer-clean: @@ -23,6 +22,4 @@ maintainer-clean: %.go: %.go.gen ./$^ > $@ -cgroup_skip_gen.go: cgroup_skip.go - .DELETE_ON_ERROR: diff --git a/sd_login/cgroup.go b/sd_login/cgroup.go new file mode 100644 index 0000000..0ceef37 --- /dev/null +++ b/sd_login/cgroup.go @@ -0,0 +1,16 @@ +package sd_login + +type _Cgroup interface { + MustSkipSystemPrefix() _Cgroup + GetSession() Session + GetOwnerUser() User + GetMachine() Machine + + GetUserSlice() string + GetUserUnit() string + + GetSlice() string + GetUnit() string +} + +func (pid PID) getCgroup() (_Cgroup, error) diff --git a/sd_login/cgroup_generic.go b/sd_login/cgroup_generic.go deleted file mode 100644 index 3842c64..0000000 --- a/sd_login/cgroup_generic.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (C) 2016-2017 Luke Shumaker -// -// 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): PID.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 PID) 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 := PID(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/cgroup_skip.go b/sd_login/cgroup_skip.go deleted file mode 100644 index 7eb2be8..0000000 --- a/sd_login/cgroup_skip.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2016-2017 Luke Shumaker -// -// 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 ( - "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/cgroup_skip_gen.go.gen b/sd_login/cgroup_skip_gen.go.gen deleted file mode 100755 index 26c515a..0000000 --- a/sd_login/cgroup_skip_gen.go.gen +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# Copyright (C) 2017 Luke Shumaker -# -# 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 < -// -// 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() Session { - unit := cgroup.GetUnit() - - session, ok := trimPrefixSuffix(unit, "session-", ".scope") - if !ok || !valid_session_name(session) { - return "" - } - - return Session(session) -} - -// XXX: who owns this, systemd or logind? elogind doesn't implement -// it, but idk -func (cgroup _Cgroup) GetOwnerUser() User { - 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 User(uid) -} - -// XXX: machined -func (cgroup _Cgroup) GetMachine() Machine { - unit := cgroup.GetUnit() - if unit == "" { - return "" - } - - machine, err := os.Readlink("/run/systemd/machines/unit:" + unit) - if err != nil { - return "" - } - return Machine(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() -} diff --git a/sd_login/doc.go b/sd_login/doc.go index ba1a0f7..33c31be 100644 --- a/sd_login/doc.go +++ b/sd_login/doc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Luke Shumaker +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,27 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package sd_login TODO +//go:generate make + +// Package sd_login introspects session and login information from +// systemd, logind, and machined. +// +// There are 5 basic object types that these three system services manage. // -// The login manager manages 4 basic object types +// The machine manager maintains a list of "machines": // -// - machine: A container or virtual machine +// 1. machine: A local container or virtual machine // -// - seat: A set of hardware devices for a workspace; ie a screen -// and keyboard +// The host system, and machines hosted on it, may each run their own +// login manager, which keeps track of seats, sessions, and users: // -// - session: A login session; tied to 0 or 1 seats (0 for things -// like SSH login) +// 2. seat: A set of hardware devices for a workspace; i.e. a screen +// and keyboard // -// - user: Keeps track of logged in users; that is, users with 1 or -// more sessions +// 3. session: A login session; tied to 0 or 1 seats (0 for things +// like SSH login) // -// TODO: why are machines part of this? +// 4. user: Keeps track of logged in users; that is, users with 1 or +// more sessions // -// - pid: A process may belong to a session, or directly to a user if -// it is not part of a session. This "belonging" is separate -// accounting by the login manager; it is NOT the same as the -// EUID/RUID. +// Finally, sessions and users can be used to group together +// processes, which are kept track of by cgroup manager: // -// - peer: A peer is a process on the other end of a AF_UNIX socket. +// 5. pid: A process may belong to a session, or directly to a user +// if it is not part of a session. This "belonging" is separate +// accounting by the login manager; it is NOT the same as the +// EUID/RUID. package sd_login diff --git a/sd_login/gen.go b/sd_login/gen.go deleted file mode 100644 index 3e5b5ac..0000000 --- a/sd_login/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:generate make - -package sd_login diff --git a/sd_login/type_machine.go b/sd_login/type_machine.go index 5892017..d89eaad 100644 --- a/sd_login/type_machine.go +++ b/sd_login/type_machine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Luke Shumaker +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,9 +21,44 @@ import ( "golang.org/x/sys/unix" ) +// A Machine is a name representing a locally running container or +// virtual machine that is registerd with (systemd-)machined. type Machine string -// running VMs/containers +// isValid returns whether the Machine name is valid. +func (m Machine) isValid() bool { + if len(m) > host_name_max { + // Note that Linux HOST_NAME_MAX is 64, but DNS allows + // 255, so names from DNS might be invalid. + return false + } + + n_dots := 0 + dot := true + for _, c := range m { + if c == '.' { + if dot { + return false + } + dot = true + n_dots++ + } else { + if !strings.ContainsRune(letters+digits+"-_.", c) { + return false + } + dot = false + } + } + + if dot { // trailing dot or empty + return false + } + + return true +} + +// GetMachines returns a list of currently running containers/virtual +// machines. func GetMachines() ([]Machine, error) { strs, err := get_files_in_directory("/run/systemd/machines/") if err != nil { @@ -31,7 +66,8 @@ func GetMachines() ([]Machine, error) { } var machines []Machine for _, str := range strs { - if strings.HasPrefix(str, "unit:") || !valid_machine_name(str) { + machine := Machine(str) + if strings.HasPrefix(str, "unit:") || machine.isValid() { continue } machines = append(machines, Machine(str)) @@ -39,7 +75,14 @@ func GetMachines() ([]Machine, error) { return machines, nil } +// GetClass returns the class of a locally running machine. The class +// is either "vm" or "container", depending on if the machine is an +// virtual machine or a container. func (m Machine) GetClass() (string, error) { + if !m.isValid() { + return "", unix.EINVAL + } + env, err := parse_env_file("/run/systemd/machines/" + string(m)) if err != nil { return "", err @@ -51,7 +94,13 @@ func (m Machine) GetClass() (string, error) { return class, nil } +// GetIfIndices returns the numeric indices of the network interfaces +// on the host that are pointing toward this machine. func (m Machine) GetIfIndices() ([]int, error) { + if !m.isValid() { + return nil, unix.EINVAL + } + env, err := parse_env_file("/run/systemd/machines/" + string(m)) if err != nil { return nil, err diff --git a/sd_login/type_monitor.go b/sd_login/type_monitor.go index 50f601a..2422f06 100644 --- a/sd_login/type_monitor.go +++ b/sd_login/type_monitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Luke Shumaker +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import ( "golang.org/x/sys/unix" ) -// Monitor monitors several types of events, so you don't have to do +// A Monitor monitors several types of events, so you don't have to do // busy-polling. // // However, Monitor won't actually give you the event that happened, @@ -84,10 +84,14 @@ func NewMonitor(categories MonitorCategory) (*Monitor, error) { return &Monitor{in: in}, nil } +// Close closes the monitor. After calling Close, further calls to +// the monitor will fail. func (m *Monitor) Close() error { return m.in.Close() } +// Flush discards any events that have happened since the last Flush +// or Wait call. func (m *Monitor) Flush() error { for { ev, err := m.in.ReadNonblock() @@ -100,6 +104,10 @@ func (m *Monitor) Flush() error { } } +// Wait blocks until a watched for event has happened. A single call +// to Wait discards only discards first event; if multiple events +// happen at once, multiple calls to Wait will immediately return +// unless Flush is called. func (m *Monitor) Wait() error { _, err := m.in.ReadBlock() return err diff --git a/sd_login/type_pid.go.gen b/sd_login/type_pid.go.gen index ec1bfe0..8d5e5fb 100755 --- a/sd_login/type_pid.go.gen +++ b/sd_login/type_pid.go.gen @@ -21,13 +21,21 @@ cat <= 0 +} + func GetPeer(conn *net.UnixConn) (PID, error) { ucred, err := getpeercred(conn) if err != nil { @@ -35,6 +43,7 @@ func GetPeer(conn *net.UnixConn) (PID, error) { } return PID(ucred.Pid), nil } + EOF data=( @@ -52,6 +61,10 @@ EOF cat < +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,9 +14,25 @@ package sd_login +import ( + "strconv" + "strings" + + "golang.org/x/sys/unix" +) + +// A Seat represents a set of hardware devices for a workspace; i.e. a +// screen and keyboard. +// +// Multiple sessions may be associated with a seat, but only one +// session may be active on a seat at a time. type Seat string -// GetSeats returns a list of all currently avialable local seats. +func (seat Seat) isValid() bool { + return valid_filename(string(seat)) +} + +// GetSeats returns a list of all currently available local seats. func GetSeats() ([]Seat, error) { strs, err := get_files_in_directory("/run/systemd/seats/") if err != nil { @@ -29,9 +45,95 @@ func GetSeats() ([]Seat, error) { return seats, nil } -func (seat Seat) GetActive() (Session, User, error) -func (seat Seat) GetSessions() ([]Session, []User, error) +// GetActive returns which session is currently active on this seat, +// or nil if there is no currently active session. +func (seat Seat) GetActive() (sess AnnotatedSession, err error) { + if !seat.isValid() { + err = unix.EINVAL + return + } + env, err := parse_env_file("/run/systemd/seats/" + string(seat)) + if err != nil { + return + } + + strName, ok := env["ACTIVE"] + if !ok { + err = unix.ENXIO + return + } + strUid, ok := env["ACTIVE_UID"] + if !ok { + err = unix.ENXIO + return + } + intUid, err := strconv.Atoi(strUid) + if err != nil { + return + } + sess.Session = Session(strName) + sess.User = User(intUid) + + return +} + +// GetSessions returns a list of all sessions associated with the +// seat, whether they are active or not. +func (seat Seat) GetSessions() ([]AnnotatedSession, error) { + if !seat.isValid() { + return nil, unix.EINVAL + } + env, err := parse_env_file("/run/systemd/seats/" + string(seat)) + if err != nil { + return nil, err + } + + strSessions, ok := env["SESSIONS"] + if !ok { + return nil, unix.ENXIO + } + strUids, ok := env["UIDS"] + if !ok { + return nil, unix.ENXIO + } + + arySessions := strings.Fields(strSessions) + aryUids := strings.Fields(strUids) + if len(arySessions) != len(aryUids) { + return nil, unix.ENXIO + } + ret := make([]AnnotatedSession, len(arySessions)) + for i := 0; i < len(arySessions); i++ { + uid, err := strconv.Atoi(aryUids[i]) + if err != nil { + return nil, err + } + ret[i].Session = Session(arySessions[i]) + ret[i].User = User(uid) + } + + return ret, nil +} + +func (seat Seat) can(cap string) (bool, error) { + if !seat.isValid() { + return false, unix.EINVAL + } + env, err := parse_env_file("/run/systemd/seats/" + string(seat)) + if err != nil { + return false, err + } + return parse_boolean(env["CAN_"+cap]) +} -func (seat Seat) CanMultiSession() bool -func (seat Seat) CanTTY() bool -func (seat Seat) CanGraphical() bool +func (seat Seat) CanMultiSession() (bool, error) { + return seat.can("MULTI_SESSION") +} + +func (seat Seat) CanTTY() (bool, error) { + return seat.can("TTY") +} + +func (seat Seat) CanGraphical() (bool, error) { + return seat.can("GRAPHICAL") +} diff --git a/sd_login/type_session.go b/sd_login/type_session.go index 47856de..e692426 100644 --- a/sd_login/type_session.go +++ b/sd_login/type_session.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Luke Shumaker +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,15 @@ package sd_login type Session string +type AnnotatedSession struct { + Session + User User +} + +func (sess AnnotatedSession) GetUser() (User, error) { + return sess.User, nil +} + type SessionState int const ( diff --git a/sd_login/util.go b/sd_login/util.go index 45df0e0..2497aef 100644 --- a/sd_login/util.go +++ b/sd_login/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Luke Shumaker +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import ( "os" "path" "strings" + + "golang.org/x/sys/unix" ) const ( @@ -97,6 +99,27 @@ func get_files_in_directory(apath string) ([]string, error) { return ret, nil } +func parse_boolean(v string) (bool, error) { + switch { + case v == "1", + strings.EqualFold(v, "yes"), + strings.EqualFold(v, "y"), + strings.EqualFold(v, "true"), + strings.EqualFold(v, "t"), + strings.EqualFold(v, "on"): + return true, nil + case v == "0", + strings.EqualFold(v, "no"), + strings.EqualFold(v, "n"), + strings.EqualFold(v, "false"), + strings.EqualFold(v, "f"), + strings.EqualFold(v, "off"): + return false, nil + default: + return false, unix.EINVAL + } +} + func parse_env_file(filename string) (map[string]string, error) /* diff --git a/sd_login/util_valid.go b/sd_login/util_valid.go index e0dd906..46493b3 100644 --- a/sd_login/util_valid.go +++ b/sd_login/util_valid.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Luke Shumaker +// Copyright (C) 2016-2017 Luke Shumaker // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,34 +30,14 @@ func valid_slice_name(s string) bool { return strings.HasSuffix(s, ".slice") && valid_unit_name(s) == unit_name_plain } -func valid_machine_name(s string) bool { - if len(s) > host_name_max { - // Note that Linux HOST_NAME_MAX is 64, but DNS allows - // 255, so names from DNS might be invalid. +func valid_filename(s string) bool { + switch s { + case "", ".", "..": return false } - - n_dots := 0 - dot := true - for _, c := range s { - if c == '.' { - if dot { - return false - } - dot = true - n_dots++ - } else { - if !strings.ContainsRune(letters+digits+"-_.", c) { - return false - } - dot = false - } - } - - if dot { // trailing dot or empty + if strings.ContainsRune(s, '/') { return false } - return true } @@ -66,9 +46,26 @@ const ( unit_name_plain = 1 << 0 // foo.service unit_name_instance = 1 << 1 // foo@bar.service unit_name_template = 1 << 2 // foo@.service - unit_name_any = unit_name_plain | unit_name_instance | unit_name_template ) +// valid_unit_name returns returns which type of unit the given unit +// name is valid for. +// +// To simply check whether a unit name is valid, without caring about +// the type, you can simply check that it doesn't return the "invalid" +// type: +// +// is_valid := valid_unit_name(unitname) != unit_name_invalid +// +// To check whether it matches a specific type (in this example, +// "template"), you can test equality against that type: +// +// is_valid_template := valid_unit_name(unitname) == unit_name_template +// +// If there are several acceptable types, you can treat multiple types +// as bitmasks, and use the usual bitfield checks: +// +// is_valid_plain_or_instance := valid_unit_name(unitname)&(unit_name_plain|unit_name_instance) != 0 func valid_unit_name(unit string) int { const_unit_name_max := 256 const_unit_types := []string{ -- cgit v1.1-4-g5e80