// Copyright 2016 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.//go:build ((unix && !android) || (js && wasm) || wasip1) && ((!cgo && !darwin) || osusergo)package userimport ()// lineFunc returns a value, an error, or (nil, nil) to skip the row.type lineFunc func(line []byte) (v any, 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.//// readCols is the minimum number of colon-separated fields that will be passed// to fn; in a long line additional fields may be silently discarded.func readColonFile( io.Reader, lineFunc, int) ( any, error) { := bufio.NewReader()// Read the file line-by-line.for {varboolvar []byte// Read the next line. We do so in chunks (as much as reader's // buffer is able to keep), check if we read enough columns // already on each step and store final result in wholeLine.for {var []byte , , = .ReadLine()if != nil {// We should return (nil, nil) if EOF is reached // without a match.if == io.EOF { = nil }returnnil, }// Simple common case: line is short enough to fit in a // single reader's buffer.if ! && len() == 0 { = break } = append(, ...)// Check if we read the whole line (or enough columns) // already.if ! || bytes.Count(, []byte{':'}) >= {break } }// 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. = bytes.TrimSpace()iflen() == 0 || [0] == '#' {continue } , = ()if != nil || != nil {return }// If necessary, skip the rest of the linefor ; ; _, , = .ReadLine() {if != nil {// We should return (nil, nil) if EOF is reached without a match.if == io.EOF { = nil }returnnil, } } }}func matchGroupIndexValue( string, int) lineFunc {varstringif > 0 { = ":" } := []byte( + + ":")returnfunc( []byte) ( any, error) {if !bytes.Contains(, ) || bytes.Count(, colon) < 3 {return }// wheel:*:0:root := strings.SplitN(string(), ":", 4)iflen() < 4 || [0] == "" || [] != ||// 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. [0][0] == '+' || [0][0] == '-' {return }if , := strconv.Atoi([2]); != nil {returnnil, nil }return &Group{Name: [0], Gid: [2]}, nil }}func findGroupId( string, io.Reader) (*Group, error) {if , := readColonFile(, matchGroupIndexValue(, 2), 3); != nil {returnnil, } elseif != nil {return .(*Group), nil }returnnil, UnknownGroupIdError()}func findGroupName( string, io.Reader) (*Group, error) {if , := readColonFile(, matchGroupIndexValue(, 0), 3); != nil {returnnil, } elseif != nil {return .(*Group), nil }returnnil, UnknownGroupError()}// returns a *User for a row if that row's has the given value at the// given index.func matchUserIndexValue( string, int) lineFunc {varstringif > 0 { = ":" } := []byte( + + ":")returnfunc( []byte) ( any, error) {if !bytes.Contains(, ) || bytes.Count(, colon) < 6 {return }// kevin:x:1005:1006::/home/kevin:/usr/bin/zsh := strings.SplitN(string(), ":", 7)iflen() < 6 || [] != || [0] == "" || [0][0] == '+' || [0][0] == '-' {return }if , := strconv.Atoi([2]); != nil {returnnil, nil }if , := strconv.Atoi([3]); != nil {returnnil, nil } := &User{Username: [0],Uid: [2],Gid: [3],Name: [4],HomeDir: [5], }// The pw_gecos field isn't quite standardized. Some docs // say: "It is expected to be a comma separated list of // personal data where the first item is the full name of the // user." .Name, _, _ = strings.Cut(.Name, ",")return , nil }}func findUserId( string, io.Reader) (*User, error) { , := strconv.Atoi()if != nil {returnnil, errors.New("user: invalid userid " + ) }if , := readColonFile(, matchUserIndexValue(, 2), 6); != nil {returnnil, } elseif != nil {return .(*User), nil }returnnil, UnknownUserIdError()}func findUsername( string, io.Reader) (*User, error) {if , := readColonFile(, matchUserIndexValue(, 0), 6); != nil {returnnil, } elseif != nil {return .(*User), nil }returnnil, UnknownUserError()}func lookupGroup( string) (*Group, error) { , := os.Open(groupFile)if != nil {returnnil, }defer .Close()returnfindGroupName(, )}func lookupGroupId( string) (*Group, error) { , := os.Open(groupFile)if != nil {returnnil, }defer .Close()returnfindGroupId(, )}func lookupUser( string) (*User, error) { , := os.Open(userFile)if != nil {returnnil, }defer .Close()returnfindUsername(, )}func lookupUserId( string) (*User, error) { , := os.Open(userFile)if != nil {returnnil, }defer .Close()returnfindUserId(, )}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.