summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--readme.go2
-rw-r--r--sd_login/.gitignore1
-rw-r--r--sd_login/Makefile3
-rw-r--r--sd_login/cgroup.go16
-rw-r--r--sd_login/cgroup_generic.go163
-rw-r--r--sd_login/cgroup_skip.go98
-rwxr-xr-xsd_login/cgroup_skip_gen.go.gen41
-rw-r--r--sd_login/cgroup_systemd.go107
-rw-r--r--sd_login/doc.go39
-rw-r--r--sd_login/gen.go3
-rw-r--r--sd_login/type_machine.go55
-rw-r--r--sd_login/type_monitor.go12
-rwxr-xr-xsd_login/type_pid.go.gen17
-rw-r--r--sd_login/type_seat.go116
-rw-r--r--sd_login/type_session.go11
-rw-r--r--sd_login/util.go25
-rw-r--r--sd_login/util_valid.go49
17 files changed, 283 insertions, 475 deletions
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 <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): 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 <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 (
- "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 <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/cgroup_systemd.go b/sd_login/cgroup_systemd.go
deleted file mode 100644
index bbef9d3..0000000
--- a/sd_login/cgroup_systemd.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// 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() 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 <lukeshu@sbcglobal.net>
+// 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.
@@ -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 <lukeshu@sbcglobal.net>
+// 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.
@@ -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 <lukeshu@sbcglobal.net>
+// 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.
@@ -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 <<EOF
package sd_login
-import "net"
+import (
+ "net"
-// PID represents a process.
+ "golang.org/x/sys/unix"
+)
+
+// A PID represents a process.
//
// As a special case, PID(0) refers to the current process.
type PID int
+func (pid PID) isValid() bool {
+ return pid >= 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 <<EOF
func (pid PID) ${Func}() (v ${Type}, err error) {
+ if !pid.isValid() {
+ err = unix.EINVAL
+ return
+ }
cgroup, err := pid.getCgroup()
if err != nil {
return
diff --git a/sd_login/type_seat.go b/sd_login/type_seat.go
index fd86663..a62effc 100644
--- a/sd_login/type_seat.go
+++ b/sd_login/type_seat.go
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+// 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.
@@ -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 <lukeshu@sbcglobal.net>
+// 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.
@@ -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 <lukeshu@sbcglobal.net>
+// 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.
@@ -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 <lukeshu@sbcglobal.net>
+// 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.
@@ -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{