github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/osutil/group.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package osutil
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"os/exec"
    26  	"os/user"
    27  	"strconv"
    28  )
    29  
    30  // FindUid returns the identifier of the given UNIX user name. It will
    31  // automatically fallback to use "getent" if needed.
    32  func FindUid(username string) (uint64, error) {
    33  	return findUid(username)
    34  }
    35  
    36  // FindGid returns the identifier of the given UNIX group name. It will
    37  // automatically fallback to use "getent" if needed.
    38  func FindGid(groupname string) (uint64, error) {
    39  	return findGid(groupname)
    40  }
    41  
    42  // getent returns the identifier of the given UNIX user or group name as
    43  // determined by the specified database
    44  func getent(database, name string) (uint64, error) {
    45  	if database != "passwd" && database != "group" {
    46  		return 0, fmt.Errorf(`unsupported getent database "%q"`, database)
    47  	}
    48  
    49  	cmdStr := []string{
    50  		"getent",
    51  		database,
    52  		name,
    53  	}
    54  	cmd := exec.Command(cmdStr[0], cmdStr[1:]...)
    55  	output, err := cmd.CombinedOutput()
    56  	if err != nil {
    57  		// according to getent(1) the exit value of "2" means:
    58  		// "One or more supplied key could not be found in the
    59  		// database."
    60  		exitCode, _ := ExitCode(err)
    61  		if exitCode == 2 {
    62  			if database == "passwd" {
    63  				return 0, user.UnknownUserError(name)
    64  			}
    65  			return 0, user.UnknownGroupError(name)
    66  		}
    67  		return 0, fmt.Errorf("getent failed with: %v", OutputErr(output, err))
    68  	}
    69  
    70  	// passwd has 7 entries and group 4. In both cases, parts[2] is the id
    71  	parts := bytes.Split(output, []byte(":"))
    72  	if len(parts) < 4 {
    73  		return 0, fmt.Errorf("malformed entry: %q", output)
    74  	}
    75  
    76  	return strconv.ParseUint(string(parts[2]), 10, 64)
    77  }
    78  
    79  var findUidNoGetentFallback = func(username string) (uint64, error) {
    80  	myuser, err := user.Lookup(username)
    81  	if err != nil {
    82  		return 0, err
    83  	}
    84  
    85  	return strconv.ParseUint(myuser.Uid, 10, 64)
    86  }
    87  
    88  var findGidNoGetentFallback = func(groupname string) (uint64, error) {
    89  	group, err := user.LookupGroup(groupname)
    90  	if err != nil {
    91  		return 0, err
    92  	}
    93  
    94  	return strconv.ParseUint(group.Gid, 10, 64)
    95  }
    96  
    97  // findUidWithGetentFallback returns the identifier of the given UNIX user name with
    98  // getent fallback
    99  func findUidWithGetentFallback(username string) (uint64, error) {
   100  	// first do the cheap os/user lookup
   101  	myuser, err := findUidNoGetentFallback(username)
   102  	switch err.(type) {
   103  	case nil:
   104  		// found it!
   105  		return myuser, nil
   106  	case user.UnknownUserError:
   107  		// user unknown, let's try getent
   108  		return getent("passwd", username)
   109  	default:
   110  		// something weird happened with the lookup, just report it
   111  		return 0, err
   112  	}
   113  }
   114  
   115  // findGidWithGetentFallback returns the identifier of the given UNIX group name with
   116  // getent fallback
   117  func findGidWithGetentFallback(groupname string) (uint64, error) {
   118  	// first do the cheap os/user lookup
   119  	group, err := findGidNoGetentFallback(groupname)
   120  	switch err.(type) {
   121  	case nil:
   122  		// found it!
   123  		return group, nil
   124  	case user.UnknownGroupError:
   125  		// group unknown, let's try getent
   126  		return getent("group", groupname)
   127  	default:
   128  		// something weird happened with the lookup, just report it
   129  		return 0, err
   130  	}
   131  }
   132  
   133  func IsUnknownUser(err error) bool {
   134  	_, ok := err.(user.UnknownUserError)
   135  	return ok
   136  }
   137  
   138  func IsUnknownGroup(err error) bool {
   139  	_, ok := err.(user.UnknownGroupError)
   140  	return ok
   141  }