diff --git a/.drone.yml b/.drone.yml index ebc2747..88d1c02 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,7 +30,7 @@ build: # TODO: make all tests work in drone! # Missing: checklists, checks, memstatus, netstatus #- godep go test ./... - - godep go test . ./chkutil ./dockerstatus ./errutil ./fsstatus ./netstatus ./systemdstatus ./tabular ./usrstatus + - godep go test . ./chkutil ./dockerstatus ./errutil ./fsstatus ./netstatus ./systemdstatus ./tabular - godep go install . - distributive --verbosity=info -d "./samples" # - distributive -d ./samples/ diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 3aa1bc0..1c54806 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -10,15 +10,44 @@ "Comment": "v0.8.7", "Rev": "418b41d23a1bf978c06faea5313ba194650ac088" }, + { + "ImportPath": "github.com/aelsabbahy/GOnetstat", + "Rev": "2907f74398ebea717cab8187513bee184b1fdd26" + }, + { + "ImportPath": "github.com/aelsabbahy/goss/system", + "Comment": "v0.1.3-3-gd28f3cc", + "Rev": "d28f3cc6d708fb012ea614acf712eb56712a7de3" + }, + { + "ImportPath": "github.com/aelsabbahy/goss/util", + "Comment": "v0.1.3-3-gd28f3cc", + "Rev": "d28f3cc6d708fb012ea614acf712eb56712a7de3" + }, { "ImportPath": "github.com/codegangsta/cli", "Comment": "1.2.0", "Rev": "565493f259bf868adb54d45d5f4c68d405117adf" }, + { + "ImportPath": "github.com/coreos/go-systemd/dbus", + "Comment": "v5", + "Rev": "7b2428fec40033549c68f54e26e89e7ca9a9ce31" + }, + { + "ImportPath": "github.com/coreos/go-systemd/util", + "Comment": "v5", + "Rev": "7b2428fec40033549c68f54e26e89e7ca9a9ce31" + }, { "ImportPath": "github.com/fsouza/go-dockerclient", "Rev": "c9ad0ce23f68428421adfc6ced9e6123f54788a5" }, + { + "ImportPath": "github.com/godbus/dbus", + "Comment": "v3-20-ge2cf281", + "Rev": "e2cf28118e66a6a63db46cf6088a35d2054d3bb0" + }, { "ImportPath": "github.com/mitchellh/go-ps", "Rev": "e6c6068076470196af082b1ff896e24a51a87b2a" @@ -31,6 +60,11 @@ "ImportPath": "github.com/mitchellh/panicwrap", "Rev": "1655d88c8ff7495ae9d2c19fd8f445f4657e22b0" }, + { + "ImportPath": "github.com/opencontainers/runc/libcontainer/user", + "Comment": "v0.0.9-82-g519529f", + "Rev": "519529febe2e0acb14e39cf54112c292ccb2eabe" + }, { "ImportPath": "golang.org/x/crypto/sha3", "Rev": "c8b9e6388ef638d5a8a9d865c634befdc46a6784" diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp.go new file mode 100644 index 0000000..c762a88 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + + "github.com/aelsabbahy/GOnetstat" +) + +/* Get TCP information and show like netstat. + Information like 'user' and 'name' of some processes will not show if you + don't have root permissions */ + +func main() { + d := GOnetstat.Tcp(false) + + // format header + fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress", + "State", "Pid/Program") + + for _, p := range d { + + // Check STATE to show only Listening connections + if p.State == "LISTEN" { + // format data like netstat output + ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port) + fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort) + pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name) + + fmt.Printf("tcp %16v %20v %16v %20v\n", ip_port, fip_port, + p.State, pid_program) + } + } +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp_json.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp_json.go new file mode 100644 index 0000000..dc3a1ce --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/tcp_json.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "encoding/json" + "github.com/drael/GOnetstat" +) + +/* Get TCP information and output in json. + Information like 'user' and 'name' of some processes will not show if you + don't have root permissions */ + +func main () { + d := GOnetstat.Tcp() + + // Marshal in prety print way + output, err := json.MarshalIndent(d, "", " ") + if err != nil { + fmt.Println(err) + } + + fmt.Println(string(output)) +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/udp.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/udp.go new file mode 100644 index 0000000..dec5843 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/Examples/udp.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/drael/GOnetstat" +) + +/* Get Udp information and show like netstat. + Information like 'user' and 'name' of some processes will not show if you + don't have root permissions */ + + +func main() { + // Get Udp data, you can use GOnetstat.Tcp() to get TCP data + d := GOnetstat.Udp() + + // format header + fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress", + "State", "Pid/Program") + + for _, p := range(d) { + // format data like netstat output + ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port) + fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort) + pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name) + + fmt.Printf("udp %16v %20v %16v %20v\n", ip_port, fip_port, + p.State, pid_program) + } +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/LICENSE.md b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/LICENSE.md new file mode 100644 index 0000000..07a2573 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Rafael Santos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/README.md b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/README.md new file mode 100644 index 0000000..8bc25f4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/README.md @@ -0,0 +1,41 @@ +# GOnetstat + +Netstat implementation in Golang. + +This Package get data from /proc/net/tcp|6 and /proc/net/udp|6 and parse +/proc/[0-9]*/fd/[0-9]* to match the correct inode. + +## Usage + +TCP/UDP +```go +tcp_data := GOnetstat.Tcp() +udp_data := GOnetstat.Udp() +``` + +This will return a array of a Process struct like this + +```go +type Process struct { + User string + Name string + Pid string + Exe string + State string + Ip string + Port int64 + ForeignIp string + ForeignPort int64 +} +``` +So you can loop through data output and format the output of your program +in whatever way you want it. +See the Examples folder! + +TCP6/UDP6 +```go +tcp6_data := GOnetstat.Tcp6() +udp6_data := GOnetstat.Udp6() +``` +The return will be a array of a Process struct like mentioned above. +Still need to create a way to compress the ipv6 because is too long. diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/gonetstat.go b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/gonetstat.go new file mode 100644 index 0000000..739b03d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/GOnetstat/gonetstat.go @@ -0,0 +1,281 @@ +/* + Simple Netstat implementation. + Get data from /proc/net/tcp and /proc/net/udp and + and parse /proc/[0-9]/fd/[0-9]. + + Author: Rafael Santos +*/ + +package GOnetstat + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/opencontainers/runc/libcontainer/user" +) + +const ( + PROC_TCP = "/proc/net/tcp" + PROC_UDP = "/proc/net/udp" + PROC_TCP6 = "/proc/net/tcp6" + PROC_UDP6 = "/proc/net/udp6" +) + +var STATE = map[string]string{ + "01": "ESTABLISHED", + "02": "SYN_SENT", + "03": "SYN_RECV", + "04": "FIN_WAIT1", + "05": "FIN_WAIT2", + "06": "TIME_WAIT", + "07": "CLOSE", + "08": "CLOSE_WAIT", + "09": "LAST_ACK", + "0A": "LISTEN", + "0B": "CLOSING", +} + +type Process struct { + User string + Name string + Pid string + Exe string + State string + Ip string + Port int64 + ForeignIp string + ForeignPort int64 +} + +func getData(t string) ([]string, error) { + // Get data from tcp or udp file. + + var proc_t string + + if t == "tcp" { + proc_t = PROC_TCP + } else if t == "udp" { + proc_t = PROC_UDP + } else if t == "tcp6" { + proc_t = PROC_TCP6 + } else if t == "udp6" { + proc_t = PROC_UDP6 + } else { + fmt.Printf("%s is a invalid type, tcp and udp only!\n", t) + os.Exit(1) + } + + data, err := ioutil.ReadFile(proc_t) + if err != nil { + return []string{}, err + } + lines := strings.Split(string(data), "\n") + + // Return lines without Header line and blank line on the end + return lines[1 : len(lines)-1], nil + +} + +func hexToDec(h string) int64 { + // convert hexadecimal to decimal. + d, err := strconv.ParseInt(h, 16, 32) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + return d +} + +func convertIp(ip string) string { + // Convert the ipv4 to decimal. Have to rearrange the ip because the + // default value is in little Endian order. + + var out string + + // Check ip size if greater than 8 is a ipv6 type + if len(ip) > 8 { + i := []string{ip[30:32], + ip[28:30], + ip[26:28], + ip[24:26], + ip[22:24], + ip[20:22], + ip[18:20], + ip[16:18], + ip[14:16], + ip[12:14], + ip[10:12], + ip[8:10], + ip[6:8], + ip[4:6], + ip[2:4], + ip[0:2]} + out = fmt.Sprintf("%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v", + i[14], i[15], i[13], i[12], + i[10], i[11], i[8], i[9], + i[6], i[7], i[4], i[5], + i[2], i[3], i[0], i[1]) + + } else { + i := []int64{hexToDec(ip[6:8]), + hexToDec(ip[4:6]), + hexToDec(ip[2:4]), + hexToDec(ip[0:2])} + + out = fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[3]) + } + return out +} + +func findPid(inode string) string { + // Loop through all fd dirs of process on /proc to compare the inode and + // get the pid. + + pid := "-" + + d, err := filepath.Glob("/proc/[0-9]*/fd/[0-9]*") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + re := regexp.MustCompile(inode) + for _, item := range d { + path, _ := os.Readlink(item) + out := re.FindString(path) + if len(out) != 0 { + pid = strings.Split(item, "/")[2] + } + } + return pid +} + +func getProcessExe(pid string) string { + exe := fmt.Sprintf("/proc/%s/exe", pid) + path, _ := os.Readlink(exe) + return path +} + +func getProcessName(exe string) string { + n := strings.Split(exe, "/") + name := n[len(n)-1] + return strings.Title(name) +} + +func getUser(uid int) string { + u, _ := user.LookupUid(uid) + return u.Name +} + +func removeEmpty(array []string) []string { + // remove empty data from line + var new_array []string + for _, i := range array { + if i != "" { + new_array = append(new_array, i) + } + } + return new_array +} + +func getInode2pid() map[string]string { + // Loop through all fd dirs of process on /proc to compare the inode and + // get the pid. + + inode2pid := make(map[string]string) + pid := "-" + + d, err := filepath.Glob("/proc/[0-9]*/fd/[0-9]*") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + var inode string + for _, item := range d { + path, _ := os.Readlink(item) + if strings.Contains(path, "socket:[") { + inode = path[8 : len(path)-1] + pid = strings.Split(item, "/")[2] + inode2pid[inode] = pid + } + } + return inode2pid +} + +func netstat(t string, lookupPids bool) ([]Process, error) { + // Return a array of Process with Name, Ip, Port, State .. etc + // Require Root acess to get information about some processes. + + var Processes []Process + + data, err := getData(t) + if err != nil { + return Processes, nil + } + var inode2pid map[string]string + if lookupPids { + inode2pid = getInode2pid() + } + + for _, line := range data { + + // local ip and port + line_array := removeEmpty(strings.Split(strings.TrimSpace(line), " ")) + ip_port := strings.Split(line_array[1], ":") + ip := convertIp(ip_port[0]) + port := hexToDec(ip_port[1]) + + // foreign ip and port + fip_port := strings.Split(line_array[2], ":") + fip := convertIp(fip_port[0]) + fport := hexToDec(fip_port[1]) + + state := STATE[line_array[3]] + uid, err := strconv.Atoi(line_array[7]) + if err != nil { + return Processes, err + } + userName := getUser(uid) + var pid, exe, name string + if lookupPids { + pid = inode2pid[line_array[9]] + exe = getProcessExe(pid) + name = getProcessName(exe) + } + + p := Process{userName, name, pid, exe, state, ip, port, fip, fport} + + Processes = append(Processes, p) + + } + + return Processes, nil +} + +func Tcp(lookupPids bool) ([]Process, error) { + // Get a slice of Process type with TCP data + return netstat("tcp", lookupPids) +} + +func Udp(lookupPids bool) ([]Process, error) { + // Get a slice of Process type with UDP data + return netstat("udp", lookupPids) +} + +func Tcp6(lookupPids bool) ([]Process, error) { + // Get a slice of Process type with TCP6 data + return netstat("tcp6", lookupPids) +} + +func Udp6(lookupPids bool) ([]Process, error) { + // Get a slice of Process type with UDP6 data + return netstat("udp6", lookupPids) +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/addr.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/addr.go new file mode 100644 index 0000000..2d6fd0b --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/addr.go @@ -0,0 +1,61 @@ +package system + +import ( + "net" + "strings" + "time" + + "github.com/aelsabbahy/goss/util" +) + +type Addr interface { + Address() string + Exists() (bool, error) + Reachable() (bool, error) +} + +type DefAddr struct { + address string + Timeout int +} + +func NewDefAddr(address string, system *System, config util.Config) Addr { + addr := normalizeAddress(address) + return &DefAddr{ + address: addr, + Timeout: config.Timeout, + } +} + +func (a *DefAddr) ID() string { + return a.address +} +func (a *DefAddr) Address() string { + return a.address +} +func (a *DefAddr) Exists() (bool, error) { return a.Reachable() } + +func (a *DefAddr) Reachable() (bool, error) { + network, address := splitAddress(a.address) + + conn, err := net.DialTimeout(network, address, time.Duration(a.Timeout)*time.Millisecond) + if err != nil { + return false, nil + } + conn.Close() + return true, nil +} + +func splitAddress(fulladdress string) (network, address string) { + split := strings.SplitN(fulladdress, "://", 2) + if len(split) == 2 { + return split[0], split[1] + } + return "tcp", fulladdress + +} + +func normalizeAddress(fulladdress string) string { + net, addr := splitAddress(fulladdress) + return net + "://" + addr +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/command.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/command.go new file mode 100644 index 0000000..802b9a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/command.go @@ -0,0 +1,104 @@ +package system + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "time" + + "github.com/aelsabbahy/goss/util" +) + +type Command interface { + Command() string + Exists() (bool, error) + ExitStatus() (int, error) + Stdout() (io.Reader, error) + Stderr() (io.Reader, error) +} + +type DefCommand struct { + command string + exitStatus int + stdout io.Reader + stderr io.Reader + loaded bool + Timeout int + err error +} + +func NewDefCommand(command string, system *System, config util.Config) Command { + return &DefCommand{ + command: command, + Timeout: config.Timeout, + } +} + +func (c *DefCommand) setup() error { + if c.loaded { + return c.err + } + c.loaded = true + + cmd := util.NewCommand("sh", "-c", c.command) + err := runCommand(cmd, c.Timeout) + + // We don't care about ExitError since it's covered by status + if _, ok := err.(*exec.ExitError); !ok { + c.err = err + } + c.exitStatus = cmd.Status + c.stdout = bytes.NewReader(cmd.Stdout.Bytes()) + c.stderr = bytes.NewReader(cmd.Stderr.Bytes()) + + return c.err +} + +func (c *DefCommand) Command() string { + return c.command +} + +func (c *DefCommand) ExitStatus() (int, error) { + err := c.setup() + + return c.exitStatus, err +} + +func (c *DefCommand) Stdout() (io.Reader, error) { + err := c.setup() + + return c.stdout, err +} + +func (c *DefCommand) Stderr() (io.Reader, error) { + err := c.setup() + + return c.stderr, err +} + +// Stub out +func (c *DefCommand) Exists() (bool, error) { + return false, nil +} + +func runCommand(cmd *util.Command, timeout int) error { + c1 := make(chan bool, 1) + e1 := make(chan error, 1) + timeoutD := time.Duration(timeout) * time.Millisecond + go func() { + err := cmd.Run() + if err != nil { + e1 <- err + } + c1 <- true + }() + select { + case <-c1: + return nil + case err := <-e1: + return err + case <-time.After(timeoutD): + return fmt.Errorf("Command execution timed out (%s)", timeoutD) + } +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/dns.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/dns.go new file mode 100644 index 0000000..38551a2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/dns.go @@ -0,0 +1,98 @@ +package system + +import ( + "fmt" + "net" + "sort" + "time" + + "github.com/aelsabbahy/goss/util" +) + +type DNS interface { + Host() string + Addrs() ([]string, error) + Resolveable() (bool, error) + Exists() (bool, error) +} + +type DefDNS struct { + host string + resolveable bool + addrs []string + Timeout int + loaded bool + err error +} + +func NewDefDNS(host string, system *System, config util.Config) DNS { + return &DefDNS{ + host: host, + Timeout: config.Timeout, + } +} + +func (d *DefDNS) Host() string { + return d.host +} + +func (d *DefDNS) setup() error { + if d.loaded { + return d.err + } + d.loaded = true + + addrs, err := lookupHost(d.host, d.Timeout) + if err != nil || len(addrs) == 0 { + d.resolveable = false + d.addrs = []string{} + // DNSError is resolvable == false, ignore error + if _, ok := err.(*net.DNSError); ok { + return nil + } + d.err = err + return d.err + } + sort.Strings(addrs) + d.resolveable = true + d.addrs = addrs + return nil +} + +func (d *DefDNS) Addrs() ([]string, error) { + err := d.setup() + + return d.addrs, err +} + +func (d *DefDNS) Resolveable() (bool, error) { + err := d.setup() + + return d.resolveable, err +} + +// Stub out +func (d *DefDNS) Exists() (bool, error) { + return false, nil +} + +func lookupHost(host string, timeout int) ([]string, error) { + c1 := make(chan []string, 1) + e1 := make(chan error, 1) + timeoutD := time.Duration(timeout) * time.Millisecond + go func() { + addrs, err := net.LookupHost(host) + if err != nil { + e1 <- err + } + c1 <- addrs + }() + select { + case res := <-c1: + return res, nil + case err := <-e1: + return nil, err + case <-time.After(timeoutD): + return nil, fmt.Errorf("DNS lookup timed out (%s)", timeoutD) + } +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/file.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/file.go new file mode 100644 index 0000000..adc3852 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/file.go @@ -0,0 +1,125 @@ +package system + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "syscall" + + "github.com/aelsabbahy/goss/util" + "github.com/opencontainers/runc/libcontainer/user" +) + +type File interface { + Path() string + Exists() (bool, error) + Contains() (io.Reader, error) + Mode() (string, error) + Filetype() (string, error) + Owner() (string, error) + Group() (string, error) + LinkedTo() (string, error) +} + +type DefFile struct { + path string +} + +func NewDefFile(path string, system *System, config util.Config) File { + absPath, _ := filepath.Abs(path) + return &DefFile{path: absPath} +} + +func (f *DefFile) Path() string { + return f.path +} + +func (f *DefFile) Exists() (bool, error) { + if _, err := os.Stat(f.path); os.IsNotExist(err) { + return false, nil + } + return true, nil +} + +func (f *DefFile) Contains() (io.Reader, error) { + fh, err := os.Open(f.path) + if err != nil { + return nil, err + } + return fh, nil +} + +func (f *DefFile) Mode() (string, error) { + fi, err := os.Lstat(f.path) + if err != nil { + return "", err + } + + return fmt.Sprintf("%#o", fi.Mode().Perm()), nil +} + +func (f *DefFile) Filetype() (string, error) { + fi, err := os.Lstat(f.path) + if err != nil { + return "", err + } + + switch { + case fi.Mode()&os.ModeSymlink == os.ModeSymlink: + return "symlink", nil + case fi.IsDir(): + return "directory", nil + case fi.Mode().IsRegular(): + return "file", nil + } + // FIXME: file as a catchall? + return "file", nil +} + +func (f *DefFile) Owner() (string, error) { + fi, err := os.Lstat(f.path) + if err != nil { + return "", err + } + + uidS := fmt.Sprint(fi.Sys().(*syscall.Stat_t).Uid) + uid, err := strconv.Atoi(uidS) + if err != nil { + return "", err + } + user, err := user.LookupUid(uid) + if err != nil { + return "", err + } + + return user.Name, nil +} + +func (f *DefFile) Group() (string, error) { + fi, err := os.Lstat(f.path) + if err != nil { + return "", err + } + + gidS := fmt.Sprint(fi.Sys().(*syscall.Stat_t).Gid) + gid, err := strconv.Atoi(gidS) + if err != nil { + return "", err + } + group, err := user.LookupGid(gid) + if err != nil { + return "", err + } + + return group.Name, nil +} + +func (f *DefFile) LinkedTo() (string, error) { + dst, err := os.Readlink(f.path) + if err != nil { + return "", err + } + return dst, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/gossfile.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/gossfile.go new file mode 100644 index 0000000..137d5a7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/gossfile.go @@ -0,0 +1,25 @@ +package system + +import "github.com/aelsabbahy/goss/util" + +type Gossfile interface { + Path() string + Exists() (bool, error) +} + +type DefGossfile struct { + path string +} + +func (g *DefGossfile) Path() string { + return g.path +} + +// Stub out +func (g *DefGossfile) Exists() (bool, error) { + return false, nil +} + +func NewDefGossfile(path string, system *System, config util.Config) Gossfile { + return &DefGossfile{path: path} +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/group.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/group.go new file mode 100644 index 0000000..9bf4817 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/group.go @@ -0,0 +1,41 @@ +package system + +import ( + "github.com/aelsabbahy/goss/util" + "github.com/opencontainers/runc/libcontainer/user" +) + +type Group interface { + Groupname() string + Exists() (bool, error) + GID() (int, error) +} + +type DefGroup struct { + groupname string +} + +func NewDefGroup(groupname string, system *System, config util.Config) Group { + return &DefGroup{groupname: groupname} +} + +func (u *DefGroup) Groupname() string { + return u.groupname +} + +func (u *DefGroup) Exists() (bool, error) { + _, err := user.LookupGroup(u.groupname) + if err != nil { + return false, nil + } + return true, nil +} + +func (u *DefGroup) GID() (int, error) { + group, err := user.LookupGroup(u.groupname) + if err != nil { + return 0, err + } + + return group.Gid, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package.go new file mode 100644 index 0000000..e9c666d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package.go @@ -0,0 +1,36 @@ +package system + +import ( + "errors" + + "github.com/aelsabbahy/goss/util" +) + +type Package interface { + Name() string + Exists() (bool, error) + Installed() (bool, error) + Versions() ([]string, error) +} + +var ErrNullPackage = errors.New("Could not detect Package type on this system, please use --package flag to explicity set it") + +type NullPackage struct { + name string +} + +func NewNullPackage(name string, system *System, config util.Config) Package { + return &NullPackage{name: name} +} + +func (p *NullPackage) Name() string { return p.name } + +func (p *NullPackage) Exists() (bool, error) { return p.Installed() } + +func (p *NullPackage) Installed() (bool, error) { + return false, ErrNullPackage +} + +func (p *NullPackage) Versions() ([]string, error) { + return nil, ErrNullPackage +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_alpine.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_alpine.go new file mode 100644 index 0000000..49ea090 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_alpine.go @@ -0,0 +1,61 @@ +package system + +import ( + "errors" + "strings" + + "github.com/aelsabbahy/goss/util" +) + +type AlpinePackage struct { + name string + versions []string + loaded bool + installed bool +} + +func NewAlpinePackage(name string, system *System, config util.Config) Package { + return &AlpinePackage{name: name} +} + +func (p *AlpinePackage) setup() { + if p.loaded { + return + } + p.loaded = true + cmd := util.NewCommand("apk", "version", p.name) + if err := cmd.Run(); err != nil { + return + } + for _, l := range strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") { + if strings.HasPrefix(l, "Installed:") || strings.HasPrefix(l, "WARNING") { + continue + } + ver := strings.TrimPrefix(strings.Fields(l)[0], p.name+"-") + p.versions = append(p.versions, ver) + } + + if len(p.versions) > 0 { + p.installed = true + } +} + +func (p *AlpinePackage) Name() string { + return p.name +} + +func (p *AlpinePackage) Exists() (bool, error) { return p.Installed() } + +func (p *AlpinePackage) Installed() (bool, error) { + p.setup() + + return p.installed, nil +} + +func (p *AlpinePackage) Versions() ([]string, error) { + p.setup() + if len(p.versions) == 0 { + return p.versions, errors.New("Package version not found") + } + return p.versions, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_deb.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_deb.go new file mode 100644 index 0000000..b222d5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_deb.go @@ -0,0 +1,61 @@ +package system + +import ( + "errors" + "strings" + + "github.com/aelsabbahy/goss/util" +) + +type DebPackage struct { + name string + versions []string + loaded bool + installed bool +} + +func NewDebPackage(name string, system *System, config util.Config) Package { + return &DebPackage{name: name} +} + +func (p *DebPackage) setup() { + if p.loaded { + return + } + p.loaded = true + cmd := util.NewCommand("dpkg-query", "-f", "${Status} ${Version}\n", "-W", p.name) + if err := cmd.Run(); err != nil { + return + } + for _, l := range strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") { + if !(strings.HasPrefix(l, "install ok installed") || strings.HasPrefix(l, "hold ok installed")) { + continue + } + ver := strings.Fields(l)[3] + p.versions = append(p.versions, ver) + } + + if len(p.versions) > 0 { + p.installed = true + } +} + +func (p *DebPackage) Name() string { + return p.name +} + +func (p *DebPackage) Exists() (bool, error) { return p.Installed() } + +func (p *DebPackage) Installed() (bool, error) { + p.setup() + + return p.installed, nil +} + +func (p *DebPackage) Versions() ([]string, error) { + p.setup() + if len(p.versions) == 0 { + return p.versions, errors.New("Package version not found") + } + return p.versions, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_rpm.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_rpm.go new file mode 100644 index 0000000..4537c33 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/package_rpm.go @@ -0,0 +1,52 @@ +package system + +import ( + "errors" + "strings" + + "github.com/aelsabbahy/goss/util" +) + +type RpmPackage struct { + name string + versions []string + loaded bool + installed bool +} + +func NewRpmPackage(name string, system *System, config util.Config) Package { + return &RpmPackage{name: name} +} + +func (p *RpmPackage) setup() { + if p.loaded { + return + } + p.loaded = true + cmd := util.NewCommand("rpm", "-q", "--nosignature", "--nohdrchk", "--nodigest", "--qf", "%{VERSION}\n", p.name) + if err := cmd.Run(); err != nil { + return + } + p.installed = true + p.versions = strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") +} + +func (p *RpmPackage) Name() string { + return p.name +} + +func (p *RpmPackage) Exists() (bool, error) { return p.Installed() } + +func (p *RpmPackage) Installed() (bool, error) { + p.setup() + + return p.installed, nil +} + +func (p *RpmPackage) Versions() ([]string, error) { + p.setup() + if len(p.versions) == 0 { + return p.versions, errors.New("Package version not found") + } + return p.versions, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/port.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/port.go new file mode 100644 index 0000000..0753be7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/port.go @@ -0,0 +1,107 @@ +package system + +import ( + "strconv" + "strings" + + "github.com/aelsabbahy/GOnetstat" + "github.com/aelsabbahy/goss/util" +) + +type Port interface { + Port() string + Exists() (bool, error) + Listening() (bool, error) + IP() ([]string, error) +} + +type DefPort struct { + port string + sysPorts map[string][]GOnetstat.Process +} + +func NewDefPort(port string, system *System, config util.Config) Port { + p := normalizePort(port) + return &DefPort{ + port: p, + sysPorts: system.Ports(), + } +} + +func splitPort(fullport string) (network, port string) { + split := strings.SplitN(fullport, ":", 2) + if len(split) == 2 { + return split[0], split[1] + } + return "tcp", fullport + +} + +func normalizePort(fullport string) string { + net, addr := splitPort(fullport) + return net + ":" + addr +} + +func (p *DefPort) Port() string { + return p.port +} + +func (p *DefPort) Exists() (bool, error) { return p.Listening() } + +func (p *DefPort) Listening() (bool, error) { + if _, ok := p.sysPorts[p.port]; ok { + return true, nil + } + return false, nil +} + +func (p *DefPort) IP() ([]string, error) { + var ips []string + for _, entry := range p.sysPorts[p.port] { + ips = append(ips, entry.Ip) + } + return ips, nil +} + +// FIXME: Is there a better way to do this rather than ignoring errors? +func GetPorts(lookupPids bool) map[string][]GOnetstat.Process { + ports := make(map[string][]GOnetstat.Process) + netstat, _ := GOnetstat.Tcp(lookupPids) + var net string + //netPorts := make(map[string]GOnetstat.Process) + //ports["tcp"] = netPorts + net = "tcp" + for _, entry := range netstat { + if entry.State == "LISTEN" { + port := strconv.FormatInt(entry.Port, 10) + ports[net+":"+port] = append(ports[net+":"+port], entry) + } + } + netstat, _ = GOnetstat.Tcp6(lookupPids) + //netPorts = make(map[string]GOnetstat.Process) + //ports["tcp6"] = netPorts + net = "tcp6" + for _, entry := range netstat { + if entry.State == "LISTEN" { + port := strconv.FormatInt(entry.Port, 10) + ports[net+":"+port] = append(ports[net+":"+port], entry) + } + } + netstat, _ = GOnetstat.Udp(lookupPids) + //netPorts = make(map[string]GOnetstat.Process) + //ports["udp"] = netPorts + net = "udp" + for _, entry := range netstat { + port := strconv.FormatInt(entry.Port, 10) + ports[net+":"+port] = append(ports[net+":"+port], entry) + } + netstat, _ = GOnetstat.Udp6(lookupPids) + //netPorts = make(map[string]GOnetstat.Process) + //ports["udp6"] = netPorts + net = "udp6" + for _, entry := range netstat { + port := strconv.FormatInt(entry.Port, 10) + ports[net+":"+port] = append(ports[net+":"+port], entry) + } + return ports +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/process.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/process.go new file mode 100644 index 0000000..cdb3b31 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/process.go @@ -0,0 +1,63 @@ +package system + +import ( + "fmt" + "os" + + "github.com/aelsabbahy/goss/util" + "github.com/mitchellh/go-ps" +) + +type Process interface { + Executable() string + Exists() (bool, error) + Running() (bool, error) + Pids() ([]int, error) +} + +type DefProcess struct { + executable string + procMap map[string][]ps.Process +} + +func NewDefProcess(executable string, system *System, config util.Config) Process { + return &DefProcess{ + executable: executable, + procMap: system.ProcMap(), + } +} + +func (p *DefProcess) Executable() string { + return p.executable +} + +func (p *DefProcess) Exists() (bool, error) { return p.Running() } + +func (p *DefProcess) Pids() ([]int, error) { + var pids []int + for _, proc := range p.procMap[p.executable] { + pids = append(pids, proc.Pid()) + } + return pids, nil +} + +func (p *DefProcess) Running() (bool, error) { + if _, ok := p.procMap[p.executable]; ok { + return true, nil + } + return false, nil +} + +func GetProcs() map[string][]ps.Process { + pmap := make(map[string][]ps.Process) + processes, err := ps.Processes() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + for _, p := range processes { + pmap[p.Executable()] = append(pmap[p.Executable()], p) + } + + return pmap +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service.go new file mode 100644 index 0000000..3a4ac97 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service.go @@ -0,0 +1,8 @@ +package system + +type Service interface { + Service() string + Exists() (bool, error) + Enabled() (bool, error) + Running() (bool, error) +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_dbus.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_dbus.go new file mode 100644 index 0000000..be21c19 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_dbus.go @@ -0,0 +1,72 @@ +package system + +import ( + "strings" + + "github.com/aelsabbahy/goss/util" + "github.com/coreos/go-systemd/dbus" +) + +type ServiceDbus struct { + service string + dbus *dbus.Conn +} + +func NewServiceDbus(service string, system *System, config util.Config) Service { + return &ServiceDbus{ + service: service, + dbus: system.Dbus(), + } +} + +func (s *ServiceDbus) Service() string { + return s.service +} + +func (s *ServiceDbus) Exists() (bool, error) { + units, err := s.dbus.ListUnits() + if err != nil { + return false, err + } + for _, u := range units { + if u.Name == s.service+".service" { + return true, nil + } + } + return false, err +} + +func (s *ServiceDbus) Enabled() (bool, error) { + stateRaw, err := s.dbus.GetUnitProperty(s.service+".service", "UnitFileState") + if err != nil { + return false, err + } + state := stateRaw.Value.String() + state = strings.Trim(state, "\"") + + if state == "enabled" { + return true, nil + } + + // Fall back on initv + if en, _ := initServiceEnabled(s.service, 3); en { + return true, nil + } + + return false, nil +} + +func (s *ServiceDbus) Running() (bool, error) { + stateRaw, err := s.dbus.GetUnitProperty(s.service+".service", "ActiveState") + if err != nil { + return false, err + } + state := stateRaw.Value.String() + state = strings.Trim(state, "\"") + + if state == "active" { + return true, nil + } + + return false, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_init.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_init.go new file mode 100644 index 0000000..336a108 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_init.go @@ -0,0 +1,66 @@ +package system + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/aelsabbahy/goss/util" +) + +type ServiceInit struct { + service string + alpine bool +} + +func NewServiceInit(service string, system *System, config util.Config) Service { + return &ServiceInit{service: service} +} + +func NewAlpineServiceInit(service string, system *System, config util.Config) Service { + return &ServiceInit{service: service, alpine: true} +} + +func (s *ServiceInit) Service() string { + return s.service +} + +func (s *ServiceInit) Exists() (bool, error) { + if _, err := os.Stat(fmt.Sprintf("/etc/init.d/%s", s.service)); err == nil { + return true, err + } + return false, nil +} + +func (s *ServiceInit) Enabled() (bool, error) { + if s.alpine { + return alpineInitServiceEnabled(s.service, "sysinit") + } else { + return initServiceEnabled(s.service, 3) + } +} + +func (s *ServiceInit) Running() (bool, error) { + cmd := util.NewCommand("service", s.service, "status") + cmd.Run() + if cmd.Status == 0 { + return true, nil + } + return false, nil +} + +func initServiceEnabled(service string, level int) (bool, error) { + matches, err := filepath.Glob(fmt.Sprintf("/etc/rc%d.d/S[0-9][0-9]%s", level, service)) + if err == nil && matches != nil { + return true, nil + } + return false, err +} + +func alpineInitServiceEnabled(service string, level string) (bool, error) { + matches, err := filepath.Glob(fmt.Sprintf("/etc/runlevels/%s/%s", level, service)) + if err == nil && matches != nil { + return true, nil + } + return false, err +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_upstart.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_upstart.go new file mode 100644 index 0000000..4febd31 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/service_upstart.go @@ -0,0 +1,67 @@ +package system + +import ( + "bufio" + "fmt" + "os" + "regexp" + "strings" + + "github.com/aelsabbahy/goss/util" +) + +type ServiceUpstart struct { + service string +} + +var upstartEnabled = regexp.MustCompile(`^\s*start on`) + +func NewServiceUpstart(service string, system *System, config util.Config) Service { + return &ServiceUpstart{service: service} +} + +func (s *ServiceUpstart) Service() string { + return s.service +} + +func (s *ServiceUpstart) Exists() (bool, error) { + // upstart + if _, err := os.Stat(fmt.Sprintf("/etc/init/%s.conf", s.service)); err == nil { + return true, err + } + + // initv + if _, err := os.Stat(fmt.Sprintf("/etc/init.d/%s", s.service)); err == nil { + return true, err + } + return false, nil +} + +func (s *ServiceUpstart) Enabled() (bool, error) { + if fh, err := os.Open(fmt.Sprintf("/etc/init/%s.conf", s.service)); err == nil { + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + line := scanner.Text() + if upstartEnabled.MatchString(line) { + return true, nil + } + } + } + + // Fall back on initv + if en, _ := initServiceEnabled(s.service, 3); en { + return true, nil + } + + return false, nil +} + +func (s *ServiceUpstart) Running() (bool, error) { + cmd := util.NewCommand("service", s.service, "status") + cmd.Run() + out := cmd.Stdout.String() + if cmd.Status == 0 && (strings.Contains(out, "running") || strings.Contains(out, "online")) { + return true, nil + } + return false, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/system.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/system.go new file mode 100644 index 0000000..f947bc2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/system.go @@ -0,0 +1,184 @@ +package system + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "sync" + + "github.com/aelsabbahy/GOnetstat" + // This needs a better name + util2 "github.com/aelsabbahy/goss/util" + "github.com/codegangsta/cli" + "github.com/coreos/go-systemd/dbus" + "github.com/coreos/go-systemd/util" + "github.com/mitchellh/go-ps" +) + +type Resource interface { + Exists() (bool, error) +} + +type System struct { + NewPackage func(string, *System, util2.Config) Package + NewFile func(string, *System, util2.Config) File + NewAddr func(string, *System, util2.Config) Addr + NewPort func(string, *System, util2.Config) Port + NewService func(string, *System, util2.Config) Service + NewUser func(string, *System, util2.Config) User + NewGroup func(string, *System, util2.Config) Group + NewCommand func(string, *System, util2.Config) Command + NewDNS func(string, *System, util2.Config) DNS + NewProcess func(string, *System, util2.Config) Process + NewGossfile func(string, *System, util2.Config) Gossfile + dbus *dbus.Conn + ports map[string][]GOnetstat.Process + dbusOnce sync.Once + portsOnce sync.Once + procOnce sync.Once + procMap map[string][]ps.Process +} + +func (s *System) Ports() map[string][]GOnetstat.Process { + s.portsOnce.Do(func() { + s.ports = GetPorts(false) + }) + return s.ports +} + +func (s *System) Dbus() *dbus.Conn { + s.dbusOnce.Do(func() { + dbus, err := dbus.New() + if err != nil { + fmt.Println(err) + // FIXME: Do we really want to exit here? + os.Exit(1) + } + s.dbus = dbus + }) + return s.dbus +} + +func (s *System) ProcMap() map[string][]ps.Process { + s.procOnce.Do(func() { + s.procMap = GetProcs() + }) + return s.procMap +} + +func New(c *cli.Context) *System { + sys := &System{ + NewFile: NewDefFile, + NewAddr: NewDefAddr, + NewPort: NewDefPort, + NewUser: NewDefUser, + NewGroup: NewDefGroup, + NewCommand: NewDefCommand, + NewDNS: NewDefDNS, + NewProcess: NewDefProcess, + NewGossfile: NewDefGossfile, + } + // FIXME: Detect-os needs to be refactored in a consistent way + // Also, cache should be its own object + sys.detectService() + + p := c.GlobalString("package") + switch p { + case "rpm": + sys.NewPackage = NewRpmPackage + case "deb": + sys.NewPackage = NewDebPackage + case "alpine": + sys.NewPackage = NewAlpinePackage + default: + sys.NewPackage = detectPackage() + } + + return sys +} + +func detectPackage() func(string, *System, util2.Config) Package { + switch { + case isRpm(): + return NewRpmPackage + case isDeb(): + return NewDebPackage + case isAlpine(): + return NewAlpinePackage + default: + return NewNullPackage + } +} + +func (s *System) detectService() { + switch { + case util.IsRunningSystemd(): + s.NewService = NewServiceDbus + case isUbuntu(): + s.NewService = NewServiceUpstart + case isAlpine(): + s.NewService = NewAlpineServiceInit + default: + s.NewService = NewServiceInit + } +} + +func isUbuntu() bool { + if b, err := ioutil.ReadFile("/etc/lsb-release"); err == nil { + if bytes.Contains(b, []byte("Ubuntu")) { + return true + } + } + return false + +} +func isDeb() bool { + if _, err := os.Stat("/etc/debian_version"); err == nil { + return true + } + + // See if it has only one of the package managers + if hasCommand("dpkg") && !hasCommand("rpm") && !hasCommand("apk") { + return true + } + + return false +} + +func isRpm() bool { + if _, err := os.Stat("/etc/redhat-release"); err == nil { + return true + } + + if _, err := os.Stat("/etc/system-release"); err == nil { + return true + } + + // See if it has only one of the package managers + if hasCommand("rpm") && !hasCommand("dpkg") && !hasCommand("apk") { + return true + } + return false +} + +func isAlpine() bool { + if _, err := os.Stat("/etc/alpine-release"); err == nil { + return true + } + + // See if it has only one of the package managers + if !hasCommand("dpkg") && !hasCommand("rpm") && hasCommand("apk") { + return true + } + + return false +} + +func hasCommand(cmd string) bool { + if _, err := exec.LookPath(cmd); err == nil { + return true + } + return false +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/user.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/user.go new file mode 100644 index 0000000..c489203 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/system/user.go @@ -0,0 +1,116 @@ +package system + +import ( + "fmt" + "sort" + + "github.com/aelsabbahy/goss/util" + "github.com/opencontainers/runc/libcontainer/user" +) + +type User interface { + Username() string + Exists() (bool, error) + UID() (int, error) + GID() (int, error) + Groups() ([]string, error) + Home() (string, error) +} + +type DefUser struct { + username string +} + +func NewDefUser(username string, system *System, config util.Config) User { + return &DefUser{username: username} +} + +func (u *DefUser) Username() string { + return u.username +} + +func (u *DefUser) Exists() (bool, error) { + _, err := user.LookupUser(u.username) + if err != nil { + return false, nil + } + return true, nil +} + +func (u *DefUser) UID() (int, error) { + user, err := user.LookupUser(u.username) + if err != nil { + return 0, err + } + + return user.Uid, nil +} + +func (u *DefUser) GID() (int, error) { + user, err := user.LookupUser(u.username) + if err != nil { + return 0, err + } + + return user.Gid, nil +} + +func (u *DefUser) Home() (string, error) { + user, err := user.LookupUser(u.username) + if err != nil { + return "", err + } + + return user.Home, nil +} + +func (u *DefUser) Groups() ([]string, error) { + user, err := user.LookupUser(u.username) + if err != nil { + return nil, err + } + + var groupList []string + groups, err := lookupUserGroups(user) + if err != nil { + return nil, err + } + + for _, g := range groups { + groupList = append(groupList, g.Name) + } + + sort.Strings(groupList) + return groupList, nil +} + +func lookupUserGroups(userS user.User) ([]user.Group, error) { + // Get operating system-specific group reader-closer. + group, err := user.GetGroup() + if err != nil { + return []user.Group{user.Group{}}, err + } + defer group.Close() + + groups, err := user.ParseGroupFilter(group, func(g user.Group) bool { + // Primary group + if g.Gid == userS.Gid { + return true + } + + // Check if user is a member. + for _, u := range g.List { + if u == userS.Name { + return true + } + } + + return false + }) + + if err != nil { + return []user.Group{user.Group{}}, fmt.Errorf("Unable to find groups for user %v: %v", userS, err) + } + + return groups, nil +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/command.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/command.go new file mode 100644 index 0000000..4d47666 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/command.go @@ -0,0 +1,45 @@ +package util + +import ( + "bytes" + //"fmt" + "os/exec" + "syscall" +) + +type Command struct { + Cmd *exec.Cmd + Stdout, Stderr bytes.Buffer + Err error + Status int +} + +func NewCommand(name string, arg ...string) *Command { + //fmt.Println(arg) + command := new(Command) + command.Cmd = exec.Command(name, arg...) + return command +} + +func (c *Command) Run() error { + c.Cmd.Stdout = &c.Stdout + c.Cmd.Stderr = &c.Stderr + + if err := c.Cmd.Start(); err != nil { + c.Err = err + //log.Fatalf("Cmd.Start: %v") + } + + if err := c.Cmd.Wait(); err != nil { + c.Err = err + if exiterr, ok := err.(*exec.ExitError); ok { + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + c.Status = status.ExitStatus() + //log.Printf("Exit Status: %d", status.ExitStatus()) + } + } + } else { + c.Status = 0 + } + return c.Err +} diff --git a/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/config.go b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/config.go new file mode 100644 index 0000000..0765e79 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aelsabbahy/goss/util/config.go @@ -0,0 +1,6 @@ +package util + +type Config struct { + IgnoreList []string + Timeout int +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go new file mode 100644 index 0000000..9404334 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go @@ -0,0 +1,198 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ +package dbus + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" + + "github.com/godbus/dbus" +) + +const ( + alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` + num = `0123456789` + alphanum = alpha + num + signalBuffer = 100 +) + +// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped +func needsEscape(i int, b byte) bool { + // Escape everything that is not a-z-A-Z-0-9 + // Also escape 0-9 if it's the first character + return strings.IndexByte(alphanum, b) == -1 || + (i == 0 && strings.IndexByte(num, b) != -1) +} + +// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the +// rules that systemd uses for serializing special characters. +func PathBusEscape(path string) string { + // Special case the empty string + if len(path) == 0 { + return "_" + } + n := []byte{} + for i := 0; i < len(path); i++ { + c := path[i] + if needsEscape(i, c) { + e := fmt.Sprintf("_%x", c) + n = append(n, []byte(e)...) + } else { + n = append(n, c) + } + } + return string(n) +} + +// Conn is a connection to systemd's dbus endpoint. +type Conn struct { + // sysconn/sysobj are only used to call dbus methods + sysconn *dbus.Conn + sysobj dbus.BusObject + + // sigconn/sigobj are only used to receive dbus signals + sigconn *dbus.Conn + sigobj dbus.BusObject + + jobListener struct { + jobs map[dbus.ObjectPath]chan<- string + sync.Mutex + } + subscriber struct { + updateCh chan<- *SubStateUpdate + errCh chan<- error + sync.Mutex + ignore map[dbus.ObjectPath]int64 + cleanIgnore int64 + } +} + +// New establishes a connection to the system bus and authenticates. +// Callers should call Close() when done with the connection. +func New() (*Conn, error) { + return newConnection(func() (*dbus.Conn, error) { + return dbusAuthHelloConnection(dbus.SystemBusPrivate) + }) +} + +// NewUserConnection establishes a connection to the session bus and +// authenticates. This can be used to connect to systemd user instances. +// Callers should call Close() when done with the connection. +func NewUserConnection() (*Conn, error) { + return newConnection(func() (*dbus.Conn, error) { + return dbusAuthHelloConnection(dbus.SessionBusPrivate) + }) +} + +// NewSystemdConnection establishes a private, direct connection to systemd. +// This can be used for communicating with systemd without a dbus daemon. +// Callers should call Close() when done with the connection. +func NewSystemdConnection() (*Conn, error) { + return newConnection(func() (*dbus.Conn, error) { + // We skip Hello when talking directly to systemd. + return dbusAuthConnection(func() (*dbus.Conn, error) { + return dbus.Dial("unix:path=/run/systemd/private") + }) + }) +} + +// Close closes an established connection +func (c *Conn) Close() { + c.sysconn.Close() + c.sigconn.Close() +} + +func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) { + sysconn, err := createBus() + if err != nil { + return nil, err + } + + sigconn, err := createBus() + if err != nil { + sysconn.Close() + return nil, err + } + + c := &Conn{ + sysconn: sysconn, + sysobj: systemdObject(sysconn), + sigconn: sigconn, + sigobj: systemdObject(sigconn), + } + + c.subscriber.ignore = make(map[dbus.ObjectPath]int64) + c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string) + + // Setup the listeners on jobs so that we can get completions + c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") + + c.dispatch() + return c, nil +} + +// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager +// interface. The value is returned in its string representation, as defined at +// https://developer.gnome.org/glib/unstable/gvariant-text.html +func (c *Conn) GetManagerProperty(prop string) (string, error) { + variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop) + if err != nil { + return "", err + } + return variant.String(), nil +} + +func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := createBus() + if err != nil { + return nil, err + } + + // Only use EXTERNAL method, and hardcode the uid (not username) + // to avoid a username lookup (which requires a dynamically linked + // libc) + methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} + + err = conn.Auth(methods) + if err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := dbusAuthConnection(createBus) + if err != nil { + return nil, err + } + + if err = conn.Hello(); err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func systemdObject(conn *dbus.Conn) dbus.BusObject { + return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go new file mode 100644 index 0000000..f9552a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go @@ -0,0 +1,442 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 dbus + +import ( + "errors" + "path" + "strconv" + + "github.com/godbus/dbus" +) + +func (c *Conn) jobComplete(signal *dbus.Signal) { + var id uint32 + var job dbus.ObjectPath + var unit string + var result string + dbus.Store(signal.Body, &id, &job, &unit, &result) + c.jobListener.Lock() + out, ok := c.jobListener.jobs[job] + if ok { + out <- result + delete(c.jobListener.jobs, job) + } + c.jobListener.Unlock() +} + +func (c *Conn) startJob(ch chan<- string, job string, args ...interface{}) (int, error) { + if ch != nil { + c.jobListener.Lock() + defer c.jobListener.Unlock() + } + + var p dbus.ObjectPath + err := c.sysobj.Call(job, 0, args...).Store(&p) + if err != nil { + return 0, err + } + + if ch != nil { + c.jobListener.jobs[p] = ch + } + + // ignore error since 0 is fine if conversion fails + jobID, _ := strconv.Atoi(path.Base(string(p))) + + return jobID, nil +} + +// StartUnit enqueues a start job and depending jobs, if any (unless otherwise +// specified by the mode string). +// +// Takes the unit to activate, plus a mode string. The mode needs to be one of +// replace, fail, isolate, ignore-dependencies, ignore-requirements. If +// "replace" the call will start the unit and its dependencies, possibly +// replacing already queued jobs that conflict with this. If "fail" the call +// will start the unit and its dependencies, but will fail if this would change +// an already queued job. If "isolate" the call will start the unit in question +// and terminate all units that aren't dependencies of it. If +// "ignore-dependencies" it will start a unit but ignore all its dependencies. +// If "ignore-requirements" it will start a unit but only ignore the +// requirement dependencies. It is not recommended to make use of the latter +// two options. +// +// If the provided channel is non-nil, a result string will be sent to it upon +// job completion: one of done, canceled, timeout, failed, dependency, skipped. +// done indicates successful execution of a job. canceled indicates that a job +// has been canceled before it finished execution. timeout indicates that the +// job timeout was reached. failed indicates that the job failed. dependency +// indicates that a job this job has been depending on failed and the job hence +// has been removed too. skipped indicates that a job was skipped because it +// didn't apply to the units current state. +// +// If no error occurs, the ID of the underlying systemd job will be returned. There +// does exist the possibility for no error to be returned, but for the returned job +// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint +// should not be considered authoritative. +// +// If an error does occur, it will be returned to the user alongside a job ID of 0. +func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode) +} + +// StopUnit is similar to StartUnit but stops the specified unit rather +// than starting it. +func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode) +} + +// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise. +func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) +} + +// RestartUnit restarts a service. If a service is restarted that isn't +// running it will be started. +func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode) +} + +// TryRestartUnit is like RestartUnit, except that a service that isn't running +// is not affected by the restart. +func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) +} + +// ReloadOrRestart attempts a reload if the unit supports it and use a restart +// otherwise. +func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) +} + +// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try" +// flavored restart otherwise. +func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) +} + +// StartTransientUnit() may be used to create and start a transient unit, which +// will be released as soon as it is not running or referenced anymore or the +// system is rebooted. name is the unit name including suffix, and must be +// unique. mode is the same as in StartUnit(), properties contains properties +// of the unit. +func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) +} + +// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's +// processes are killed. +func (c *Conn) KillUnit(name string, signal int32) { + c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store() +} + +// ResetFailedUnit resets the "failed" state of a specific unit. +func (c *Conn) ResetFailedUnit(name string) error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store() +} + +// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface +func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) { + var err error + var props map[string]dbus.Variant + + path := unitPath(unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) + if err != nil { + return nil, err + } + + out := make(map[string]interface{}, len(props)) + for k, v := range props { + out[k] = v.Value() + } + + return out, nil +} + +// GetUnitProperties takes the unit name and returns all of its dbus object properties. +func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { + return c.getProperties(unit, "org.freedesktop.systemd1.Unit") +} + +func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) { + var err error + var prop dbus.Variant + + path := unitPath(unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) + if err != nil { + return nil, err + } + + return &Property{Name: propertyName, Value: prop}, nil +} + +func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName) +} + +// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type. +// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope +// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { + return c.getProperties(unit, "org.freedesktop.systemd1."+unitType) +} + +// SetUnitProperties() may be used to modify certain unit properties at runtime. +// Not all properties may be changed at runtime, but many resource management +// settings (primarily those in systemd.cgroup(5)) may. The changes are applied +// instantly, and stored on disk for future boots, unless runtime is true, in which +// case the settings only apply until the next reboot. name is the name of the unit +// to modify. properties are the settings to set, encoded as an array of property +// name and value pairs. +func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() +} + +func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName) +} + +type UnitStatus struct { + Name string // The primary unit name as string + Description string // The human readable description string + LoadState string // The load state (i.e. whether the unit file has been loaded successfully) + ActiveState string // The active state (i.e. whether the unit is currently started or not) + SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) + Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. + Path dbus.ObjectPath // The unit object path + JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise + JobType string // The job type as string + JobPath dbus.ObjectPath // The job object path +} + +// ListUnits returns an array with all currently loaded units. Note that +// units may be known by multiple names at the same time, and hence there might +// be more unit names loaded than actual units behind them. +func (c *Conn) ListUnits() ([]UnitStatus, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + status := make([]UnitStatus, len(result)) + statusInterface := make([]interface{}, len(status)) + for i := range status { + statusInterface[i] = &status[i] + } + + err = dbus.Store(resultInterface, statusInterface...) + if err != nil { + return nil, err + } + + return status, nil +} + +type UnitFile struct { + Path string + Type string +} + +// ListUnitFiles returns an array of all available units on disk. +func (c *Conn) ListUnitFiles() ([]UnitFile, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + files := make([]UnitFile, len(result)) + fileInterface := make([]interface{}, len(files)) + for i := range files { + fileInterface[i] = &files[i] + } + + err = dbus.Store(resultInterface, fileInterface...) + if err != nil { + return nil, err + } + + return files, nil +} + +type LinkUnitFileChange EnableUnitFileChange + +// LinkUnitFiles() links unit files (that are located outside of the +// usual unit search paths) into the unit search path. +// +// It takes a list of absolute paths to unit files to link and two +// booleans. The first boolean controls whether the unit shall be +// enabled for runtime only (true, /run), or persistently (false, +// /etc). +// The second controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns a list of the changes made. The list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]LinkUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +// EnableUnitFiles() may be used to enable one or more units in the system (by +// creating symlinks to them in /etc or /run). +// +// It takes a list of unit files to enable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and two booleans: the first controls whether the unit shall +// be enabled for runtime only (true, /run), or persistently (false, /etc). +// The second one controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns one boolean and an array with the changes made. The +// boolean signals whether the unit files contained any enablement +// information (i.e. an [Install]) section. The changes list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { + var carries_install_info bool + + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) + if err != nil { + return false, nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]EnableUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return false, nil, err + } + + return carries_install_info, changes, nil +} + +type EnableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// DisableUnitFiles() may be used to disable one or more units in the system (by +// removing symlinks to them from /etc or /run). +// +// It takes a list of unit files to disable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and one boolean: whether the unit was enabled for runtime +// only (true, /run), or persistently (false, /etc). +// +// This call returns an array with the changes made. The changes list +// consists of structures with three strings: the type of the change (one of +// symlink or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]DisableUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +type DisableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Reload instructs systemd to scan for and reload unit files. This is +// equivalent to a 'systemctl daemon-reload'. +func (c *Conn) Reload() error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store() +} + +func unitPath(name string) dbus.ObjectPath { + return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name)) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go new file mode 100644 index 0000000..7520011 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go @@ -0,0 +1,218 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 dbus + +import ( + "github.com/godbus/dbus" +) + +// From the systemd docs: +// +// The properties array of StartTransientUnit() may take many of the settings +// that may also be configured in unit files. Not all parameters are currently +// accepted though, but we plan to cover more properties with future release. +// Currently you may set the Description, Slice and all dependency types of +// units, as well as RemainAfterExit, ExecStart for service units, +// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, +// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, +// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, +// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map +// directly to their counterparts in unit files and as normal D-Bus object +// properties. The exception here is the PIDs field of scope units which is +// used for construction of the scope only and specifies the initial PIDs to +// add to the scope object. + +type Property struct { + Name string + Value dbus.Variant +} + +type PropertyCollection struct { + Name string + Properties []Property +} + +type execStart struct { + Path string // the binary path to execute + Args []string // an array with all arguments to pass to the executed command, starting with argument 0 + UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly +} + +// PropExecStart sets the ExecStart service property. The first argument is a +// slice with the binary path to execute followed by the arguments to pass to +// the executed command. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= +func PropExecStart(command []string, uncleanIsFailure bool) Property { + execStarts := []execStart{ + execStart{ + Path: command[0], + Args: command, + UncleanIsFailure: uncleanIsFailure, + }, + } + + return Property{ + Name: "ExecStart", + Value: dbus.MakeVariant(execStarts), + } +} + +// PropRemainAfterExit sets the RemainAfterExit service property. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= +func PropRemainAfterExit(b bool) Property { + return Property{ + Name: "RemainAfterExit", + Value: dbus.MakeVariant(b), + } +} + +// PropDescription sets the Description unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= +func PropDescription(desc string) Property { + return Property{ + Name: "Description", + Value: dbus.MakeVariant(desc), + } +} + +func propDependency(name string, units []string) Property { + return Property{ + Name: name, + Value: dbus.MakeVariant(units), + } +} + +// PropRequires sets the Requires unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= +func PropRequires(units ...string) Property { + return propDependency("Requires", units) +} + +// PropRequiresOverridable sets the RequiresOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= +func PropRequiresOverridable(units ...string) Property { + return propDependency("RequiresOverridable", units) +} + +// PropRequisite sets the Requisite unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= +func PropRequisite(units ...string) Property { + return propDependency("Requisite", units) +} + +// PropRequisiteOverridable sets the RequisiteOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= +func PropRequisiteOverridable(units ...string) Property { + return propDependency("RequisiteOverridable", units) +} + +// PropWants sets the Wants unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= +func PropWants(units ...string) Property { + return propDependency("Wants", units) +} + +// PropBindsTo sets the BindsTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= +func PropBindsTo(units ...string) Property { + return propDependency("BindsTo", units) +} + +// PropRequiredBy sets the RequiredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= +func PropRequiredBy(units ...string) Property { + return propDependency("RequiredBy", units) +} + +// PropRequiredByOverridable sets the RequiredByOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= +func PropRequiredByOverridable(units ...string) Property { + return propDependency("RequiredByOverridable", units) +} + +// PropWantedBy sets the WantedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= +func PropWantedBy(units ...string) Property { + return propDependency("WantedBy", units) +} + +// PropBoundBy sets the BoundBy unit property. See +// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= +func PropBoundBy(units ...string) Property { + return propDependency("BoundBy", units) +} + +// PropConflicts sets the Conflicts unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= +func PropConflicts(units ...string) Property { + return propDependency("Conflicts", units) +} + +// PropConflictedBy sets the ConflictedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= +func PropConflictedBy(units ...string) Property { + return propDependency("ConflictedBy", units) +} + +// PropBefore sets the Before unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= +func PropBefore(units ...string) Property { + return propDependency("Before", units) +} + +// PropAfter sets the After unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= +func PropAfter(units ...string) Property { + return propDependency("After", units) +} + +// PropOnFailure sets the OnFailure unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= +func PropOnFailure(units ...string) Property { + return propDependency("OnFailure", units) +} + +// PropTriggers sets the Triggers unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= +func PropTriggers(units ...string) Property { + return propDependency("Triggers", units) +} + +// PropTriggeredBy sets the TriggeredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= +func PropTriggeredBy(units ...string) Property { + return propDependency("TriggeredBy", units) +} + +// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= +func PropPropagatesReloadTo(units ...string) Property { + return propDependency("PropagatesReloadTo", units) +} + +// PropRequiresMountsFor sets the RequiresMountsFor unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= +func PropRequiresMountsFor(units ...string) Property { + return propDependency("RequiresMountsFor", units) +} + +// PropSlice sets the Slice unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= +func PropSlice(slice string) Property { + return Property{ + Name: "Slice", + Value: dbus.MakeVariant(slice), + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go new file mode 100644 index 0000000..f92e6fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go @@ -0,0 +1,47 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 dbus + +type set struct { + data map[string]bool +} + +func (s *set) Add(value string) { + s.data[value] = true +} + +func (s *set) Remove(value string) { + delete(s.data, value) +} + +func (s *set) Contains(value string) (exists bool) { + _, exists = s.data[value] + return +} + +func (s *set) Length() int { + return len(s.data) +} + +func (s *set) Values() (values []string) { + for val, _ := range s.data { + values = append(values, val) + } + return +} + +func newSet() *set { + return &set{make(map[string]bool)} +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go new file mode 100644 index 0000000..9964514 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go @@ -0,0 +1,250 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 dbus + +import ( + "errors" + "time" + + "github.com/godbus/dbus" +) + +const ( + cleanIgnoreInterval = int64(10 * time.Second) + ignoreInterval = int64(30 * time.Millisecond) +) + +// Subscribe sets up this connection to subscribe to all systemd dbus events. +// This is required before calling SubscribeUnits. When the connection closes +// systemd will automatically stop sending signals so there is no need to +// explicitly call Unsubscribe(). +func (c *Conn) Subscribe() error { + c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") + c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") + + err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() + if err != nil { + return err + } + + return nil +} + +// Unsubscribe this connection from systemd dbus events. +func (c *Conn) Unsubscribe() error { + err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() + if err != nil { + return err + } + + return nil +} + +func (c *Conn) dispatch() { + ch := make(chan *dbus.Signal, signalBuffer) + + c.sigconn.Signal(ch) + + go func() { + for { + signal, ok := <-ch + if !ok { + return + } + + if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" { + c.jobComplete(signal) + } + + if c.subscriber.updateCh == nil { + continue + } + + var unitPath dbus.ObjectPath + switch signal.Name { + case "org.freedesktop.systemd1.Manager.JobRemoved": + unitName := signal.Body[2].(string) + c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) + case "org.freedesktop.systemd1.Manager.UnitNew": + unitPath = signal.Body[1].(dbus.ObjectPath) + case "org.freedesktop.DBus.Properties.PropertiesChanged": + if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { + unitPath = signal.Path + } + } + + if unitPath == dbus.ObjectPath("") { + continue + } + + c.sendSubStateUpdate(unitPath) + } + }() +} + +// Returns two unbuffered channels which will receive all changed units every +// interval. Deleted units are sent as nil. +func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { + return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) +} + +// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer +// size of the channels, the comparison function for detecting changes and a filter +// function for cutting down on the noise that your channel receives. +func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { + old := make(map[string]*UnitStatus) + statusChan := make(chan map[string]*UnitStatus, buffer) + errChan := make(chan error, buffer) + + go func() { + for { + timerChan := time.After(interval) + + units, err := c.ListUnits() + if err == nil { + cur := make(map[string]*UnitStatus) + for i := range units { + if filterUnit != nil && filterUnit(units[i].Name) { + continue + } + cur[units[i].Name] = &units[i] + } + + // add all new or changed units + changed := make(map[string]*UnitStatus) + for n, u := range cur { + if oldU, ok := old[n]; !ok || isChanged(oldU, u) { + changed[n] = u + } + delete(old, n) + } + + // add all deleted units + for oldN := range old { + changed[oldN] = nil + } + + old = cur + + if len(changed) != 0 { + statusChan <- changed + } + } else { + errChan <- err + } + + <-timerChan + } + }() + + return statusChan, errChan +} + +type SubStateUpdate struct { + UnitName string + SubState string +} + +// SetSubStateSubscriber writes to updateCh when any unit's substate changes. +// Although this writes to updateCh on every state change, the reported state +// may be more recent than the change that generated it (due to an unavoidable +// race in the systemd dbus interface). That is, this method provides a good +// way to keep a current view of all units' states, but is not guaranteed to +// show every state transition they go through. Furthermore, state changes +// will only be written to the channel with non-blocking writes. If updateCh +// is full, it attempts to write an error to errCh; if errCh is full, the error +// passes silently. +func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { + c.subscriber.Lock() + defer c.subscriber.Unlock() + c.subscriber.updateCh = updateCh + c.subscriber.errCh = errCh +} + +func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) { + c.subscriber.Lock() + defer c.subscriber.Unlock() + + if c.shouldIgnore(path) { + return + } + + info, err := c.GetUnitProperties(string(path)) + if err != nil { + select { + case c.subscriber.errCh <- err: + default: + } + } + + name := info["Id"].(string) + substate := info["SubState"].(string) + + update := &SubStateUpdate{name, substate} + select { + case c.subscriber.updateCh <- update: + default: + select { + case c.subscriber.errCh <- errors.New("update channel full!"): + default: + } + } + + c.updateIgnore(path, info) +} + +// The ignore functions work around a wart in the systemd dbus interface. +// Requesting the properties of an unloaded unit will cause systemd to send a +// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's +// properties on UnitNew (as that's the only indication of a new unit coming up +// for the first time), we would enter an infinite loop if we did not attempt +// to detect and ignore these spurious signals. The signal themselves are +// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an +// unloaded unit's signals for a short time after requesting its properties. +// This means that we will miss e.g. a transient unit being restarted +// *immediately* upon failure and also a transient unit being started +// immediately after requesting its status (with systemctl status, for example, +// because this causes a UnitNew signal to be sent which then causes us to fetch +// the properties). + +func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { + t, ok := c.subscriber.ignore[path] + return ok && t >= time.Now().UnixNano() +} + +func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { + c.cleanIgnore() + + // unit is unloaded - it will trigger bad systemd dbus behavior + if info["LoadState"].(string) == "not-found" { + c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval + } +} + +// without this, ignore would grow unboundedly over time +func (c *Conn) cleanIgnore() { + now := time.Now().UnixNano() + if c.subscriber.cleanIgnore < now { + c.subscriber.cleanIgnore = now + cleanIgnoreInterval + + for p, t := range c.subscriber.ignore { + if t < now { + delete(c.subscriber.ignore, p) + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go new file mode 100644 index 0000000..5b408d5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go @@ -0,0 +1,57 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 dbus + +import ( + "time" +) + +// SubscriptionSet returns a subscription set which is like conn.Subscribe but +// can filter to only return events for a set of units. +type SubscriptionSet struct { + *set + conn *Conn +} + +func (s *SubscriptionSet) filter(unit string) bool { + return !s.Contains(unit) +} + +// Subscribe starts listening for dbus events for all of the units in the set. +// Returns channels identical to conn.SubscribeUnits. +func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { + // TODO: Make fully evented by using systemd 209 with properties changed values + return s.conn.SubscribeUnitsCustom(time.Second, 0, + mismatchUnitStatus, + func(unit string) bool { return s.filter(unit) }, + ) +} + +// NewSubscriptionSet returns a new subscription set. +func (conn *Conn) NewSubscriptionSet() *SubscriptionSet { + return &SubscriptionSet{newSet(), conn} +} + +// mismatchUnitStatus returns true if the provided UnitStatus objects +// are not equivalent. false is returned if the objects are equivalent. +// Only the Name, Description and state-related fields are used in +// the comparison. +func mismatchUnitStatus(u1, u2 *UnitStatus) bool { + return u1.Name != u2.Name || + u1.Description != u2.Description || + u1.LoadState != u2.LoadState || + u1.ActiveState != u2.ActiveState || + u1.SubState != u2.SubState +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/util/util.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/util/util.go new file mode 100644 index 0000000..f9f0b2a --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/util/util.go @@ -0,0 +1,270 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 util contains utility functions related to systemd that applications +// can use to check things like whether systemd is running. Note that some of +// these functions attempt to manually load systemd libraries at runtime rather +// than linking against them. +package util + +// #cgo LDFLAGS: -ldl +// #include +// #include +// #include +// #include +// +// int +// my_sd_pid_get_owner_uid(void *f, pid_t pid, uid_t *uid) +// { +// int (*sd_pid_get_owner_uid)(pid_t, uid_t *); +// +// sd_pid_get_owner_uid = (int (*)(pid_t, uid_t *))f; +// return sd_pid_get_owner_uid(pid, uid); +// } +// +// int +// my_sd_pid_get_unit(void *f, pid_t pid, char **unit) +// { +// int (*sd_pid_get_unit)(pid_t, char **); +// +// sd_pid_get_unit = (int (*)(pid_t, char **))f; +// return sd_pid_get_unit(pid, unit); +// } +// +// int +// my_sd_pid_get_slice(void *f, pid_t pid, char **slice) +// { +// int (*sd_pid_get_slice)(pid_t, char **); +// +// sd_pid_get_slice = (int (*)(pid_t, char **))f; +// return sd_pid_get_slice(pid, slice); +// } +// +// int +// am_session_leader() +// { +// return (getsid(0) == getpid()); +// } +import "C" +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "syscall" + "unsafe" +) + +var ErrSoNotFound = errors.New("unable to open a handle to libsystemd") + +// libHandle represents an open handle to the systemd C library +type libHandle struct { + handle unsafe.Pointer + libname string +} + +func (h *libHandle) Close() error { + if r := C.dlclose(h.handle); r != 0 { + return fmt.Errorf("error closing %v: %d", h.libname, r) + } + return nil +} + +// getHandle tries to get a handle to a systemd library (.so), attempting to +// access it by several different names and returning the first that is +// successfully opened. Callers are responsible for closing the handler. +// If no library can be successfully opened, an error is returned. +func getHandle() (*libHandle, error) { + for _, name := range []string{ + // systemd < 209 + "libsystemd-login.so", + "libsystemd-login.so.0", + + // systemd >= 209 merged libsystemd-login into libsystemd proper + "libsystemd.so", + "libsystemd.so.0", + } { + libname := C.CString(name) + defer C.free(unsafe.Pointer(libname)) + handle := C.dlopen(libname, C.RTLD_LAZY) + if handle != nil { + h := &libHandle{ + handle: handle, + libname: name, + } + return h, nil + } + } + return nil, ErrSoNotFound +} + +// GetRunningSlice attempts to retrieve the name of the systemd slice in which +// the current process is running. +// This function is a wrapper around the libsystemd C library; if it cannot be +// opened, an error is returned. +func GetRunningSlice() (slice string, err error) { + var h *libHandle + h, err = getHandle() + if err != nil { + return + } + defer func() { + if err1 := h.Close(); err1 != nil { + err = err1 + } + }() + + sym := C.CString("sd_pid_get_slice") + defer C.free(unsafe.Pointer(sym)) + sd_pid_get_slice := C.dlsym(h.handle, sym) + if sd_pid_get_slice == nil { + err = fmt.Errorf("error resolving sd_pid_get_slice function") + return + } + + var s string + sl := C.CString(s) + defer C.free(unsafe.Pointer(sl)) + + ret := C.my_sd_pid_get_slice(sd_pid_get_slice, 0, &sl) + if ret < 0 { + err = fmt.Errorf("error calling sd_pid_get_slice: %v", syscall.Errno(-ret)) + return + } + + return C.GoString(sl), nil +} + +// RunningFromSystemService tries to detect whether the current process has +// been invoked from a system service. The condition for this is whether the +// process is _not_ a user process. User processes are those running in session +// scopes or under per-user `systemd --user` instances. +// +// To avoid false positives on systems without `pam_systemd` (which is +// responsible for creating user sessions), this function also uses a heuristic +// to detect whether it's being invoked from a session leader process. This is +// the case if the current process is executed directly from a service file +// (e.g. with `ExecStart=/this/cmd`). Note that this heuristic will fail if the +// command is instead launched in a subshell or similar so that it is not +// session leader (e.g. `ExecStart=/bin/bash -c "/this/cmd"`) +// +// This function is a wrapper around the libsystemd C library; if this is +// unable to successfully open a handle to the library for any reason (e.g. it +// cannot be found), an errr will be returned +func RunningFromSystemService() (ret bool, err error) { + var h *libHandle + h, err = getHandle() + if err != nil { + return + } + defer func() { + if err1 := h.Close(); err1 != nil { + err = err1 + } + }() + + sym := C.CString("sd_pid_get_owner_uid") + defer C.free(unsafe.Pointer(sym)) + sd_pid_get_owner_uid := C.dlsym(h.handle, sym) + if sd_pid_get_owner_uid == nil { + err = fmt.Errorf("error resolving sd_pid_get_owner_uid function") + return + } + + var uid C.uid_t + errno := C.my_sd_pid_get_owner_uid(sd_pid_get_owner_uid, 0, &uid) + serrno := syscall.Errno(-errno) + // when we're running from a unit file, sd_pid_get_owner_uid returns + // ENOENT (systemd <220) or ENXIO (systemd >=220) + switch { + case errno >= 0: + ret = false + case serrno == syscall.ENOENT, serrno == syscall.ENXIO: + // Since the implementation of sessions in systemd relies on + // the `pam_systemd` module, using the sd_pid_get_owner_uid + // heuristic alone can result in false positives if that module + // (or PAM itself) is not present or properly configured on the + // system. As such, we also check if we're the session leader, + // which should be the case if we're invoked from a unit file, + // but not if e.g. we're invoked from the command line from a + // user's login session + ret = C.am_session_leader() == 1 + default: + err = fmt.Errorf("error calling sd_pid_get_owner_uid: %v", syscall.Errno(-errno)) + } + return +} + +// CurrentUnitName attempts to retrieve the name of the systemd system unit +// from which the calling process has been invoked. It wraps the systemd +// `sd_pid_get_unit` call, with the same caveat: for processes not part of a +// systemd system unit, this function will return an error. +func CurrentUnitName() (unit string, err error) { + var h *libHandle + h, err = getHandle() + if err != nil { + return + } + defer func() { + if err1 := h.Close(); err1 != nil { + err = err1 + } + }() + + sym := C.CString("sd_pid_get_unit") + defer C.free(unsafe.Pointer(sym)) + sd_pid_get_unit := C.dlsym(h.handle, sym) + if sd_pid_get_unit == nil { + err = fmt.Errorf("error resolving sd_pid_get_unit function") + return + } + + var s string + u := C.CString(s) + defer C.free(unsafe.Pointer(u)) + + ret := C.my_sd_pid_get_unit(sd_pid_get_unit, 0, &u) + if ret < 0 { + err = fmt.Errorf("error calling sd_pid_get_unit: %v", syscall.Errno(-ret)) + return + } + + unit = C.GoString(u) + return +} + +// 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() +} + +// GetMachineID returns a host's 128-bit machine ID as a string. This functions +// similarly to systemd's `sd_id128_get_machine`: internally, it simply reads +// the contents of /etc/machine-id +// http://www.freedesktop.org/software/systemd/man/sd_id128_get_machine.html +func GetMachineID() (string, error) { + machineID, err := ioutil.ReadFile("/etc/machine-id") + if err != nil { + return "", fmt.Errorf("failed to read /etc/machine-id: %v", err) + } + return strings.TrimSpace(string(machineID)), nil +} diff --git a/Godeps/_workspace/src/github.com/godbus/dbus/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/godbus/dbus/CONTRIBUTING.md new file mode 100644 index 0000000..c88f9b2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/godbus/dbus/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +