// +build !go1.7 package mtree import ( "bufio" "bytes" "io" "os" "strconv" "strings" ) const groupFile = "/etc/group" var colon = []byte{':'} // Group represents a grouping of users. // // On POSIX systems Gid contains a decimal number representing the group ID. type Group struct { Gid string // group ID Name string // group name } func lookupGroupID(id string) (*Group, error) { f, err := os.Open(groupFile) if err != nil { return nil, err } defer f.Close() return findGroupID(id, f) } func findGroupID(id string, r io.Reader) (*Group, error) { if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil { return nil, err } else if v != nil { return v.(*Group), nil } return nil, UnknownGroupIDError(id) } // lineFunc returns a value, an error, or (nil, nil) to skip the row. type lineFunc func(line []byte) (v interface{}, err error) // readColonFile parses r as an /etc/group or /etc/passwd style file, running // fn for each row. readColonFile returns a value, an error, or (nil, nil) if // the end of the file is reached without a match. func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) { bs := bufio.NewScanner(r) for bs.Scan() { line := bs.Bytes() // There's no spec for /etc/passwd or /etc/group, but we try to follow // the same rules as the glibc parser, which allows comments and blank // space at the beginning of a line. line = bytes.TrimSpace(line) if len(line) == 0 || line[0] == '#' { continue } v, err = fn(line) if v != nil || err != nil { return } } return nil, bs.Err() } func matchGroupIndexValue(value string, idx int) lineFunc { var leadColon string if idx > 0 { leadColon = ":" } substr := []byte(leadColon + value + ":") return func(line []byte) (v interface{}, err error) { if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 { return } // wheel:*:0:root parts := strings.SplitN(string(line), ":", 4) if len(parts) < 4 || parts[0] == "" || parts[idx] != value || // If the file contains +foo and you search for "foo", glibc // returns an "invalid argument" error. Similarly, if you search // for a gid for a row where the group name starts with "+" or "-", // glibc fails to find the record. parts[0][0] == '+' || parts[0][0] == '-' { return } if _, err := strconv.Atoi(parts[2]); err != nil { return nil, nil } return &Group{Name: parts[0], Gid: parts[2]}, nil } } // UnknownGroupIDError is returned by LookupGroupId when // a group cannot be found. type UnknownGroupIDError string func (e UnknownGroupIDError) Error() string { return "group: unknown groupid " + string(e) }