package systemd import ( "fmt" "os" "strings" "sync" "time" systemdDbus "github.com/coreos/go-systemd/v22/dbus" dbus "github.com/godbus/dbus/v5" "github.com/opencontainers/runc/libcontainer/configs" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) var ( connOnce sync.Once connDbus *systemdDbus.Conn connErr error ) // NOTE: This function comes from package github.com/coreos/go-systemd/util // It was borrowed here to avoid a dependency on cgo. // // IsRunningSystemd checks whether the host was booted with systemd as its init // system. This functions similarly to systemd's `sd_booted(3)`: internally, it // checks whether /run/systemd/system/ exists and is a directory. // http://www.freedesktop.org/software/systemd/man/sd_booted.html func IsRunningSystemd() bool { fi, err := os.Lstat("/run/systemd/system") if err != nil { return false } return fi.IsDir() } // systemd represents slice hierarchy using `-`, so we need to follow suit when // generating the path of slice. Essentially, test-a-b.slice becomes // /test.slice/test-a.slice/test-a-b.slice. func ExpandSlice(slice string) (string, error) { suffix := ".slice" // Name has to end with ".slice", but can't be just ".slice". if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) { return "", fmt.Errorf("invalid slice name: %s", slice) } // Path-separators are not allowed. if strings.Contains(slice, "/") { return "", fmt.Errorf("invalid slice name: %s", slice) } var path, prefix string sliceName := strings.TrimSuffix(slice, suffix) // if input was -.slice, we should just return root now if sliceName == "-" { return "/", nil } for _, component := range strings.Split(sliceName, "-") { // test--a.slice isn't permitted, nor is -test.slice. if component == "" { return "", fmt.Errorf("invalid slice name: %s", slice) } // Append the component to the path and to the prefix. path += "/" + prefix + component + suffix prefix += component + "-" } return path, nil } // getDbusConnection lazy initializes systemd dbus connection // and returns it func getDbusConnection(rootless bool) (*systemdDbus.Conn, error) { connOnce.Do(func() { if rootless { connDbus, connErr = NewUserSystemdDbus() } else { connDbus, connErr = systemdDbus.New() } }) return connDbus, connErr } func newProp(name string, units interface{}) systemdDbus.Property { return systemdDbus.Property{ Name: name, Value: dbus.MakeVariant(units), } } func getUnitName(c *configs.Cgroup) string { // by default, we create a scope unless the user explicitly asks for a slice. if !strings.HasSuffix(c.Name, ".slice") { return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name) } return c.Name } // isUnitExists returns true if the error is that a systemd unit already exists. func isUnitExists(err error) bool { if err != nil { if dbusError, ok := err.(dbus.Error); ok { return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists") } } return false } func startUnit(dbusConnection *systemdDbus.Conn, unitName string, properties []systemdDbus.Property) error { statusChan := make(chan string, 1) if _, err := dbusConnection.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil { select { case s := <-statusChan: close(statusChan) // Please refer to https://godoc.org/github.com/coreos/go-systemd/dbus#Conn.StartUnit if s != "done" { dbusConnection.ResetFailedUnit(unitName) return errors.Errorf("error creating systemd unit `%s`: got `%s`", unitName, s) } case <-time.After(time.Second): logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", unitName) } } else if !isUnitExists(err) { return err } return nil } func stopUnit(dbusConnection *systemdDbus.Conn, unitName string) error { statusChan := make(chan string, 1) if _, err := dbusConnection.StopUnit(unitName, "replace", statusChan); err == nil { select { case s := <-statusChan: close(statusChan) // Please refer to https://godoc.org/github.com/coreos/go-systemd/dbus#Conn.StartUnit if s != "done" { logrus.Warnf("error removing unit `%s`: got `%s`. Continuing...", unitName, s) } case <-time.After(time.Second): logrus.Warnf("Timed out while waiting for StopUnit(%s) completion signal from dbus. Continuing...", unitName) } } return nil }