forked from golang/go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add unix, plan9, windows users groups iteration to os/user
- Loading branch information
1 parent
ff1cd5d
commit ca4f26b
Showing
18 changed files
with
1,111 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package user | ||
|
||
// NextUserFunc is used in users iteration process. It receives *User for each user record. | ||
// If non-nil error is returned from NextUserFunc - iteration process is terminated. | ||
type NextUserFunc func(*User) error | ||
|
||
// NextGroupFunc is used in groups iteration process. It receives *Group for each group record. | ||
// If non-nil error is returned from NextGroupFunc - iteration process is terminated. | ||
type NextGroupFunc func(*Group) error | ||
|
||
// IterateUsers iterates over user entries. For each retrieved *User entry provided NextUserFunc is called. | ||
// | ||
// On UNIX, if CGO is enabled, getpwent is used in the underlying implementation. Since getpwent is not thread-safe, | ||
// locking is strongly advised. | ||
func IterateUsers(n NextUserFunc) error { | ||
return iterateUsers(n) | ||
} | ||
|
||
// IterateGroups iterates over group entries. For each retrieved *Group entry provided NextGroupFunc is called. | ||
// | ||
// On UNIX, if CGO is enabled, getgrent is used in the underlying implementation. Since getgrent is not thread-safe, | ||
// locking is strongly advised. | ||
func IterateGroups(n NextGroupFunc) error { | ||
return iterateGroups(n) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
//go:build (aix || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris || darwin) && cgo && !osusergo | ||
// +build aix dragonfly freebsd !android,linux netbsd openbsd solaris darwin | ||
// +build cgo | ||
// +build !osusergo | ||
|
||
package user | ||
|
||
/* | ||
#include <unistd.h> | ||
#include <sys/types.h> | ||
#include <pwd.h> | ||
#include <grp.h> | ||
#include <stdlib.h> | ||
#include <stdio.h> | ||
#include <errno.h> | ||
static void resetErrno(){ | ||
errno = 0; | ||
} | ||
*/ | ||
import "C" | ||
|
||
// usersHelper defines the methods used in users iteration process within iterateUsers. This interface allows testing | ||
// iterateUsers functionality. iterate_test_fgetent.go file defines test related struct that implements usersHelper. | ||
type usersHelper interface { | ||
// set sets up internal state before iteration | ||
set() | ||
|
||
// get sequentially returns a passwd structure which is later processed into *User entry | ||
get() (*C.struct_passwd, error) | ||
|
||
// end cleans up internal state after iteration is done | ||
end() | ||
} | ||
|
||
type iterateUsersHelper struct{} | ||
|
||
func (i iterateUsersHelper) set() { | ||
C.setpwent() | ||
} | ||
|
||
func (i iterateUsersHelper) get() (*C.struct_passwd, error) { | ||
var result *C.struct_passwd | ||
result, err := C.getpwent() | ||
return result, err | ||
} | ||
|
||
func (i iterateUsersHelper) end() { | ||
C.endpwent() | ||
} | ||
|
||
// This helper is used to retrieve users via c library call. A global | ||
// variable which implements usersHelper interface is needed in order to | ||
// separate testing logic from production. Since cgo can not be used directly | ||
// in tests, iterate_test_fgetent.go file provides iterateUsersHelperTest | ||
// structure which implements usersHelper interface and can substitute | ||
// default iuh value. | ||
var iuh usersHelper = iterateUsersHelper{} | ||
|
||
// iterateUsers iterates over users database via getpwent(3). If fn returns non nil error, then | ||
// iteration is terminated. A nil result from getpwent means there were no more entries, or an error occurred, | ||
// as such, iteration is terminated, and if error was encountered it is returned. | ||
// | ||
// Since iterateUsers uses getpwent(3), which is not thread safe, iterateUsers can not bet used concurrently. | ||
// If concurrent usage is required, it is recommended to use locking mechanism such as sync.Mutex when calling | ||
// iterateUsers from multiple goroutines. | ||
func iterateUsers(fn NextUserFunc) error { | ||
iuh.set() | ||
defer iuh.end() | ||
for { | ||
var result *C.struct_passwd | ||
C.resetErrno() | ||
result, err := iuh.get() | ||
|
||
// If result is nil - getpwent iterated through entire users database or there was an error | ||
if result == nil { | ||
return err | ||
} | ||
|
||
// User provided non-nil error means that iteration should be terminated | ||
if err = fn(buildUser(result)); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
// groupsHelper defines the methods used in groups iteration process within iterateGroups. This interface allows testing | ||
// iterateGroups functionality. iterate_test_fgetent.go file defines test related struct that implements groupsHelper. | ||
type groupsHelper interface { | ||
// set sets up internal state before iteration | ||
set() | ||
|
||
// get sequentially returns a group structure which is later processed into *Group entry | ||
get() (*C.struct_group, error) | ||
|
||
// end cleans up internal state after iteration is done | ||
end() | ||
} | ||
|
||
type iterateGroupsHelper struct{} | ||
|
||
func (i iterateGroupsHelper) set() { | ||
C.setgrent() | ||
} | ||
|
||
func (i iterateGroupsHelper) get() (*C.struct_group, error) { | ||
var result *C.struct_group | ||
result, err := C.getgrent() | ||
return result, err | ||
} | ||
|
||
func (i iterateGroupsHelper) end() { | ||
C.endgrent() | ||
} | ||
|
||
// This helper is used to retrieve groups via c library call. A global | ||
// variable which implements groupsHelper interface is needed in order to | ||
// separate testing logic from production. Since cgo can not be used directly | ||
// in tests, iterate_test_fgetent.go file provides iterateGroupsHelperTest | ||
// structure which implements groupsHelper interface and can substitute | ||
// default igh value. | ||
var igh groupsHelper = iterateGroupsHelper{} | ||
|
||
// iterateGroups iterates over groups database via getgrent(3). If fn returns non nil error, then | ||
// iteration is terminated. A nil result from getgrent means there were no more entries, or an error occurred, | ||
// as such, iteration is terminated, and if error was encountered it is returned. | ||
// | ||
// Since iterateGroups uses getgrent(3), which is not thread safe, iterateGroups can not bet used concurrently. | ||
// If concurrent usage is required, it is recommended to use locking mechanism such as sync.Mutex when calling | ||
// iterateGroups from multiple goroutines. | ||
func iterateGroups(fn NextGroupFunc) error { | ||
igh.set() | ||
defer igh.end() | ||
for { | ||
var result *C.struct_group | ||
C.resetErrno() | ||
result, err := igh.get() | ||
|
||
// If result is nil - getgrent iterated through entire groups database or there was an error | ||
if result == nil { | ||
return err | ||
} | ||
|
||
// User provided non-nil error means that iteration should be terminated | ||
if err = fn(buildGroup(result)); err != nil { | ||
return err | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
//go:build (darwin || freebsd || openbsd || netbsd) && cgo && !osusergo | ||
// +build darwin freebsd openbsd netbsd | ||
// +build cgo | ||
// +build !osusergo | ||
|
||
package user | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
) | ||
|
||
// As cgo implementation can not be tested on darwin or bsd based operating | ||
// systems, since there is no support for fgetpwent/fgetgrent library calls, | ||
// we will attempt to check if user/group record could be at least retrieved | ||
|
||
var _stopErr = errors.New("terminate iteration") | ||
|
||
func TestIterateUser(t *testing.T) { | ||
err := iterateUsers(func(user *User) error { | ||
if user.Username == "" && user.Gid == "" && user.Uid == "" && user.HomeDir == "" && user.Name == "" { | ||
t.Errorf("parsed user is empty: %+v", user) | ||
} | ||
return _stopErr | ||
}) | ||
|
||
if err != _stopErr { | ||
t.Errorf("iterating users: %w", err) | ||
} | ||
} | ||
|
||
func TestIterateGroup(t *testing.T) { | ||
err := iterateGroups(func(group *Group) error { | ||
if group.Name == "" && group.Gid == "" { | ||
t.Errorf("parsed group is empty: %+v", group) | ||
} | ||
return _stopErr | ||
}) | ||
|
||
if err != _stopErr { | ||
t.Errorf("iterating groups: %w", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
//go:build (aix || dragonfly || (!android && linux) || solaris) && cgo && !osusergo | ||
// +build aix dragonfly !android,linux solaris | ||
// +build cgo | ||
// +build !osusergo | ||
|
||
package user | ||
|
||
import ( | ||
"reflect" | ||
"syscall" | ||
"testing" | ||
) | ||
|
||
func TestIterateUser(t *testing.T) { | ||
var wantsUsers = []*User{ | ||
{Uid: "0", Gid: "0", Username: "root", Name: "System Administrator", HomeDir: "/var/root"}, | ||
{Uid: "1", Gid: "1", Username: "daemon", Name: "System Services", HomeDir: "/var/root"}, | ||
{Uid: "4", Gid: "4", Username: "_uucp", Name: "Unix to Unix Copy Protocol", HomeDir: "/var/spool/uucp"}, | ||
{Uid: "13", Gid: "13", Username: "_taskgated", Name: "Task Gate Daemon", HomeDir: "/var/empty"}, | ||
{Uid: "24", Gid: "24", Username: "_networkd", Name: "Network Services", HomeDir: "/var/networkd"}, | ||
{Uid: "25", Gid: "25", Username: "_installassistant", Name: "Install Assistant", HomeDir: "/var/empty"}, | ||
{Uid: "26", Gid: "26", Username: "_lp", Name: "Printing Services", HomeDir: "/var/spool/cups"}, | ||
{Uid: "27", Gid: "27", Username: "_postfix", Name: "Postfix Mail Server", HomeDir: "/var/spool/postfix"}, | ||
} | ||
|
||
iuh = &iterateUsersHelperTest{} | ||
|
||
// Test that users are retrieved in same order as defined | ||
gotUsers := make([]*User, 0, len(wantsUsers)) | ||
err := iterateUsers(func(user *User) error { | ||
gotUsers = append(gotUsers, user) | ||
return nil | ||
}) | ||
|
||
if err != syscall.ENOENT { | ||
t.Errorf("iterating users: %v", err) | ||
} | ||
|
||
if len(gotUsers) != len(wantsUsers) || !reflect.DeepEqual(wantsUsers, gotUsers) { | ||
t.Errorf("could not parse all users correctly") | ||
} | ||
} | ||
|
||
func TestIterateGroup(t *testing.T) { | ||
var wantsGroups = []*Group{ | ||
{Gid: "0", Name: "wheel"}, | ||
{Gid: "1", Name: "daemon"}, | ||
{Gid: "2", Name: "kmem"}, | ||
{Gid: "3", Name: "sys"}, | ||
{Gid: "5", Name: "operator"}, | ||
{Gid: "6", Name: "mail"}, | ||
{Gid: "4", Name: "tty"}, | ||
{Gid: "7", Name: "bin"}, | ||
{Gid: "8", Name: "procview"}, | ||
{Gid: "9", Name: "procmod"}, | ||
{Gid: "10", Name: "owner"}, | ||
{Gid: "12", Name: "everyone"}, | ||
} | ||
|
||
// Use testdata fixture | ||
igh = &iterateGroupsHelperTest{} | ||
|
||
// Test that groups are retrieved in same order as defined | ||
gotGroups := make([]*Group, 0, len(wantsGroups)) | ||
err := iterateGroups(func(g *Group) error { | ||
gotGroups = append(gotGroups, g) | ||
return nil | ||
}) | ||
|
||
if err != syscall.ENOENT { | ||
t.Errorf("iterating groups: %v", err) | ||
} | ||
|
||
if len(gotGroups) != len(wantsGroups) || !reflect.DeepEqual(wantsGroups, gotGroups) { | ||
t.Errorf("could not parse all groups correctly") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package user | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
func ExampleIterateUsers() { | ||
// Get first 20 users | ||
users := make([]*User, 0, 20) | ||
i := 0 | ||
err := IterateUsers(func(user *User) error { | ||
users = append(users, user) | ||
i++ | ||
|
||
// Once we return non-nil error - iteration process stops | ||
if i >= 20 { | ||
return errors.New("stop iterating") | ||
} | ||
|
||
// As long as error is nil, IterateUsers will iterate over users database | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
fmt.Printf("error encountered while iterating users database: %v", err) | ||
} | ||
|
||
// Here users slice can be used to do something with collected users. | ||
} | ||
|
||
func ExampleIterateGroups() { | ||
// Get first 20 groups | ||
groups := make([]*Group, 0, 20) | ||
i := 0 | ||
err := IterateGroups(func(group *Group) error { | ||
groups = append(groups, group) | ||
i++ | ||
|
||
// Once we return non-nil error - iteration process stops | ||
if i >= 20 { | ||
return errors.New("stop iterating") | ||
} | ||
|
||
// As long as error is nil, IterateGroups will iterate over groups database | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
fmt.Printf("error encountered while iterating groups database: %v", err) | ||
} | ||
|
||
// Here groups slice can be used to do something with collected groups. | ||
} |
Oops, something went wrong.