From 7a0de27c26a25b359f99d6172e7b867cc91d5d10 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 30 Jun 2026 12:02:50 +0200 Subject: [PATCH 1/2] user: add parseUserArg and parseGroupArg utilities Signed-off-by: Sebastiaan van Stijn --- user/user.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/user/user.go b/user/user.go index 1fd9a08..5783807 100644 --- a/user/user.go +++ b/user/user.go @@ -408,13 +408,59 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( return user, nil } -// groupArg is a parsed group argument for [GetAdditionalGroups]. +// userArg is a parsed user argument for [GetExecUser]. +type userArg struct { + name string + uid int + isNumeric bool +} + +// parseUserArg parses a user argument as either a user name or UID. +// +// If name is empty, parseUserArg returns "nil, nil" to indicate that no user +// argument was specified. +func parseUserArg(name string) (*userArg, error) { + if name == "" { + return nil, nil + } + uid, isNumeric, err := parseNumeric(name) + if err != nil { + return nil, err + } + return &userArg{name: name, uid: uid, isNumeric: isNumeric}, nil +} + +// matches reports whether group u satisfies the argument. Numeric arguments +// are matched by UID only, others by name. +func (ua *userArg) matches(u User) bool { + if ua.isNumeric { + return u.Uid == ua.uid + } + return u.Name == ua.name +} + +// groupArg is a parsed group argument for [GetAdditionalGroups] or [GetExecUser]. type groupArg struct { name string gid int isNumeric bool } +// parseGroupArg parses a group argument as either a group name or GID. +// +// If name is empty, parseGroupArg returns "nil, nil" to indicate that no group +// argument was specified. +func parseGroupArg(name string) (*groupArg, error) { + if name == "" { + return nil, nil + } + gid, isNumeric, err := parseNumeric(name) + if err != nil { + return nil, err + } + return &groupArg{name: name, gid: gid, isNumeric: isNumeric}, nil +} + // matches reports whether group g satisfies the argument. Numeric arguments // are matched by GID only, others by name. func (ag groupArg) matches(g Group) bool { From ac163915ebc29b4b00eb7384b3ecbee0a9b9e7a0 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 30 Jun 2026 13:13:18 +0200 Subject: [PATCH 2/2] user: GetExecUser: rewrite with parseUserArg, parseGroupArg Signed-off-by: Sebastiaan van Stijn --- user/user.go | 55 +++++++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/user/user.go b/user/user.go index 5783807..a6de5d5 100644 --- a/user/user.go +++ b/user/user.go @@ -301,40 +301,35 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( } // Allow for userSpec to have either "user", or optionally "user:group" syntax. - userArg, groupArg, _ := strings.Cut(userSpec, ":") + usr, grp, _ := strings.Cut(userSpec, ":") // Convert userArg and groupArg to be numeric, so we don't have to execute // Atoi *twice* for each iteration over lines. - uidArg, isUID, err := parseNumeric(userArg) + userArg, err := parseUserArg(usr) if err != nil { return nil, err } - gidArg, isGID, err := parseNumeric(groupArg) + groupArg, err := parseGroupArg(grp) if err != nil { return nil, err } // Find the matching user. users, err := ParsePasswdFilter(passwd, func(u User) bool { - if userArg == "" { + if userArg == nil { // Default to current state of the user. return u.Uid == user.Uid } - - if isUID { - // If the userArg is a valid numeric value, always treat it as a UID. - return uidArg == u.Uid - } - - return u.Name == userArg + return userArg.matches(u) }) // If we can't find the user, we have to bail. if err != nil && passwd != nil { - if userArg == "" { - userArg = strconv.Itoa(user.Uid) + name := usr + if userArg == nil { + name = strconv.Itoa(user.Uid) } - return nil, fmt.Errorf("unable to find user %s: %w", userArg, err) + return nil, fmt.Errorf("unable to find user %s: %w", name, err) } var matchedUserName string @@ -344,23 +339,21 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( user.Uid = users[0].Uid user.Gid = users[0].Gid user.Home = users[0].Home - } else if userArg != "" { + } else if userArg != nil { // If we can't find a user with the given username, the only other valid // option is if it's a numeric username with no associated entry in passwd. - - if !isUID { - // Not numeric. - return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries) + if !userArg.isNumeric { + return nil, fmt.Errorf("unable to find user %s: %w", userArg.name, ErrNoPasswdEntries) } - user.Uid = uidArg + user.Uid = userArg.uid } // On to the groups. If we matched a username, we need to do this because of // the supplementary group IDs. - if groupArg != "" || matchedUserName != "" { + if groupArg != nil || matchedUserName != "" { groups, err := ParseGroupFilter(group, func(g Group) bool { // If the group argument isn't explicit, we'll just search for it. - if groupArg == "" { + if groupArg == nil { // Check if user is a member of this group. for _, u := range g.List { if u == matchedUserName { @@ -369,32 +362,24 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( } return false } - - if isGID { - // If the groupArg is numeric, always treat it as a GID. - return gidArg == g.Gid - } - - return g.Name == groupArg + return groupArg.matches(g) }) if err != nil && group != nil { return nil, fmt.Errorf("unable to find groups for spec %v: %w", matchedUserName, err) } // Only start modifying user.Gid if it is in explicit form. - if groupArg != "" { + if groupArg != nil { if len(groups) > 0 { // First match wins, even if there's more than one matching entry. user.Gid = groups[0].Gid } else { // If we can't find a group with the given name, the only other valid // option is if it's a numeric group name with no associated entry in group. - - if !isGID { - // Not numeric. - return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries) + if !groupArg.isNumeric { + return nil, fmt.Errorf("unable to find group %s: %w", groupArg.name, ErrNoGroupEntries) } - user.Gid = gidArg + user.Gid = groupArg.gid } } else if len(groups) > 0 { // Supplementary group ids only make sense if in the implicit form.