Skip to content

Commit

Permalink
osutil: unify osutil.Find{Uid,Gid} with automatic getent fallback
Browse files Browse the repository at this point in the history
The fact that osutil.Find{Uid,Gid} was behaving differently
depending on if it is build with cgo or not is confusing. So
instead of providing Find{Uid,Gid}Getent() unify this into
a single function that automatically falls back to getent if
we build without cgo.
  • Loading branch information
mvo5 committed Aug 16, 2019
1 parent 12bf462 commit 7dded6f
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 66 deletions.
16 changes: 6 additions & 10 deletions osutil/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,10 @@ func MockUname(f func(*syscall.Utsname) error) (restore func()) {
}
}

func MockFindUid(mock func(name string) (uint64, error)) (restore func()) {
old := FindUid
FindUid = mock
return func() { FindUid = old }
}
var (
FindUidNoGetentFallback = findUidNoGetentFallback
FindGidNoGetentFallback = findGidNoGetentFallback

func MockFindGid(mock func(name string) (uint64, error)) (restore func()) {
old := FindGid
FindGid = mock
return func() { FindGid = old }
}
FindUidWithGetentFallback = findUidWithGetentFallback
FindGidWithGetentFallback = findGidWithGetentFallback
)
64 changes: 30 additions & 34 deletions osutil/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,16 @@ import (
"strconv"
)

var (
FindUid = findUid
FindGid = findGid
FindUidGetent = findUidGetent
FindGidGetent = findGidGetent
)

// The builtin os/user functions only look at /etc/passwd and /etc/group and
// nothing configured via nsswitch.conf, like extrausers. findUid() and
// findGid() continue this behavior where findUidGetent() and findGidGetent()
// will perform a 'getent <database> <name>'

// findUid returns the identifier of the given UNIX user name with no getent
// FindUid returns the identifier of the given UNIX user name with no getent
// fallback
func findUid(username string) (uint64, error) {
myuser, err := user.Lookup(username)
if err != nil {
return 0, err
}

return strconv.ParseUint(myuser.Uid, 10, 64)
func FindUid(username string) (uint64, error) {
return findUid(username)
}

// findGid returns the identifier of the given UNIX group name with no getent
// FindGid returns the identifier of the given UNIX group name with no getent
// fallback
func findGid(groupname string) (uint64, error) {
group, err := user.LookupGroup(groupname)
if err != nil {
return 0, err
}

return strconv.ParseUint(group.Gid, 10, 64)
func FindGid(groupname string) (uint64, error) {
return findGid(groupname)
}

// getent returns the identifier of the given UNIX user or group name as
Expand Down Expand Up @@ -98,11 +76,29 @@ func getent(name string, database string) (uint64, error) {
return strconv.ParseUint(string(parts[2]), 10, 64)
}

// findUidGetent returns the identifier of the given UNIX user name with
func findUidNoGetentFallback(username string) (uint64, error) {
myuser, err := user.Lookup(username)
if err != nil {
return 0, err
}

return strconv.ParseUint(myuser.Uid, 10, 64)
}

func findGidNoGetentFallback(groupname string) (uint64, error) {
group, err := user.LookupGroup(groupname)
if err != nil {
return 0, err
}

return strconv.ParseUint(group.Gid, 10, 64)
}

// findUidWithGetentFallback returns the identifier of the given UNIX user name with
// getent fallback
func findUidGetent(username string) (uint64, error) {
func findUidWithGetentFallback(username string) (uint64, error) {
// first do the cheap os/user lookup
myuser, err := FindUid(username)
myuser, err := findUidNoGetentFallback(username)
if err == nil {
// found it!
return myuser, nil
Expand All @@ -115,11 +111,11 @@ func findUidGetent(username string) (uint64, error) {
return getent(username, "passwd")
}

// findGidGetent returns the identifier of the given UNIX group name with
// findGidWithGetentFallback returns the identifier of the given UNIX group name with
// getent fallback
func findGidGetent(groupname string) (uint64, error) {
func findGidWithGetentFallback(groupname string) (uint64, error) {
// first do the cheap os/user lookup
group, err := FindGid(groupname)
group, err := findGidNoGetentFallback(groupname)
if err == nil {
// found it!
return group, nil
Expand Down
29 changes: 29 additions & 0 deletions osutil/group_cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
// +build cgo

/*
* Copyright (C) 2017-2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package osutil

// The builtin os/user functions use the libc functions when compiled
// with cgo so we use use them here.

var (
findUid = findUidNoGetentFallback
findGid = findGidNoGetentFallback
)
17 changes: 17 additions & 0 deletions osutil/group_no_cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
// +build !cgo

package osutil

// The builtin os/user functions only look at /etc/passwd and
// /etc/group when building without cgo.
//
// So nothing configured via nsswitch.conf, like extrausers. findUid()
// and findGid() is searched. To fix this behavior we use
// find{Uid,Gid}WithGetentFallback() that perform a 'getent
// <database> <name>' automatically if no local user is found.

var (
findUid = findUidWithGetentFallback
findGid = findGidWithGetentFallback
)
42 changes: 20 additions & 22 deletions osutil/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,33 @@ func (s *findUserGroupSuite) TearDownTest(c *check.C) {
s.mockGetent.Restore()
}

func (s *findUserGroupSuite) TestFindUid(c *check.C) {
uid, err := osutil.FindUid("root")
func (s *findUserGroupSuite) TestFindUidNoGetentFallback(c *check.C) {
uid, err := osutil.FindUidNoGetentFallback("root")
c.Assert(err, check.IsNil)
c.Assert(uid, check.Equals, uint64(0))
// getent shouldn't have been called with FindUid()
// getent shouldn't have been called with FindUidNoGetentFallback()
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string(nil))
}

func (s *findUserGroupSuite) TestFindUidNonexistent(c *check.C) {
_, err := osutil.FindUid("lakatos")
_, err := osutil.FindUidNoGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, "user: unknown user lakatos")
_, ok := err.(user.UnknownUserError)
c.Assert(ok, check.Equals, true)
// getent shouldn't have been called with FindUid()
// getent shouldn't have been called with FindUidNoGetentFallback()
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string(nil))
}

func (s *findUserGroupSuite) TestFindUidGetent(c *check.C) {
uid, err := osutil.FindUidGetent("root")
func (s *findUserGroupSuite) TestFindUidWithGetentFallback(c *check.C) {
uid, err := osutil.FindUidWithGetentFallback("root")
c.Assert(err, check.IsNil)
c.Assert(uid, check.Equals, uint64(0))
// getent shouldn't have been called since 'root' is in /etc/passwd
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string(nil))
}

func (s *findUserGroupSuite) TestFindUidGetentNonexistent(c *check.C) {
_, err := osutil.FindUidGetent("lakatos")
_, err := osutil.FindUidWithGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, "user: unknown user lakatos")
_, ok := err.(user.UnknownUserError)
c.Assert(ok, check.Equals, true)
Expand All @@ -83,7 +83,7 @@ func (s *findUserGroupSuite) TestFindUidGetentNonexistent(c *check.C) {
func (s *findUserGroupSuite) TestFindUidGetentMockedOtherError(c *check.C) {
s.mockGetent = testutil.MockCommand(c, "getent", "exit 3")

uid, err := osutil.FindUidGetent("lakatos")
uid, err := osutil.FindUidWithGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, "cannot run getent: exit status 3")
c.Check(uid, check.Equals, uint64(0))
// getent should've have been called
Expand All @@ -95,7 +95,7 @@ func (s *findUserGroupSuite) TestFindUidGetentMockedOtherError(c *check.C) {
func (s *findUserGroupSuite) TestFindUidGetentMocked(c *check.C) {
s.mockGetent = testutil.MockCommand(c, "getent", "echo lakatos:x:1234:5678:::")

uid, err := osutil.FindUidGetent("lakatos")
uid, err := osutil.FindUidWithGetentFallback("lakatos")
c.Assert(err, check.IsNil)
c.Check(uid, check.Equals, uint64(1234))
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string{
Expand All @@ -106,37 +106,35 @@ func (s *findUserGroupSuite) TestFindUidGetentMocked(c *check.C) {
func (s *findUserGroupSuite) TestFindUidGetentMockedMalformated(c *check.C) {
s.mockGetent = testutil.MockCommand(c, "getent", "printf too:few:colons")

_, err := osutil.FindUidGetent("lakatos")
_, err := osutil.FindUidWithGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, `malformed entry: "too:few:colons"`)
}

func (s *findUserGroupSuite) TestFindGid(c *check.C) {
uid, err := osutil.FindGid("root")
func (s *findUserGroupSuite) TestFindGidNoGetentFallback(c *check.C) {
uid, err := osutil.FindGidNoGetentFallback("root")
c.Assert(err, check.IsNil)
c.Assert(uid, check.Equals, uint64(0))
// getent shouldn't have been called with FindGid()
// getent shouldn't have been called with FindGidNoGetentFallback()
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string(nil))
}

func (s *findUserGroupSuite) TestFindGidNonexistent(c *check.C) {
_, err := osutil.FindGid("lakatos")
_, err := osutil.FindGidNoGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, "group: unknown group lakatos")
_, ok := err.(user.UnknownGroupError)
c.Assert(ok, check.Equals, true)
// getent shouldn't have been called with FindGid()
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string(nil))
}

func (s *findUserGroupSuite) TestFindGidGetent(c *check.C) {
uid, err := osutil.FindGidGetent("root")
func (s *findUserGroupSuite) TestFindGidWithGetentFallback(c *check.C) {
uid, err := osutil.FindGidWithGetentFallback("root")
c.Assert(err, check.IsNil)
c.Assert(uid, check.Equals, uint64(0))
// getent shouldn't have been called since 'root' is in /etc/passwd
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string(nil))
}

func (s *findUserGroupSuite) TestFindGidGetentNonexistent(c *check.C) {
_, err := osutil.FindGidGetent("lakatos")
_, err := osutil.FindGidWithGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, "group: unknown group lakatos")
_, ok := err.(user.UnknownGroupError)
c.Assert(ok, check.Equals, true)
Expand All @@ -149,7 +147,7 @@ func (s *findUserGroupSuite) TestFindGidGetentNonexistent(c *check.C) {
func (s *findUserGroupSuite) TestFindGidGetentMockedOtherError(c *check.C) {
s.mockGetent = testutil.MockCommand(c, "getent", "exit 3")

gid, err := osutil.FindGidGetent("lakatos")
gid, err := osutil.FindGidWithGetentFallback("lakatos")
c.Assert(err, check.ErrorMatches, "cannot run getent: exit status 3")
c.Check(gid, check.Equals, uint64(0))
// getent should've have been called
Expand All @@ -161,7 +159,7 @@ func (s *findUserGroupSuite) TestFindGidGetentMockedOtherError(c *check.C) {
func (s *findUserGroupSuite) TestFindGidGetentMocked(c *check.C) {
s.mockGetent = testutil.MockCommand(c, "getent", "echo lakatos:x:1234:")

uid, err := osutil.FindGidGetent("lakatos")
uid, err := osutil.FindGidWithGetentFallback("lakatos")
c.Assert(err, check.IsNil)
c.Check(uid, check.Equals, uint64(1234))
c.Check(s.mockGetent.Calls(), check.DeepEquals, [][]string{
Expand Down

0 comments on commit 7dded6f

Please sign in to comment.