github.com/kunnos/engine@v1.13.1/pkg/idtools/idtools_unix.go (about)

     1  // +build !windows
     2  
     3  package idtools
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/docker/docker/pkg/system"
    15  	"github.com/opencontainers/runc/libcontainer/user"
    16  )
    17  
    18  var (
    19  	entOnce   sync.Once
    20  	getentCmd string
    21  )
    22  
    23  func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
    24  	// make an array containing the original path asked for, plus (for mkAll == true)
    25  	// all path components leading up to the complete path that don't exist before we MkdirAll
    26  	// so that we can chown all of them properly at the end.  If chownExisting is false, we won't
    27  	// chown the full directory path if it exists
    28  	var paths []string
    29  	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
    30  		paths = []string{path}
    31  	} else if err == nil && chownExisting {
    32  		if err := os.Chown(path, ownerUID, ownerGID); err != nil {
    33  			return err
    34  		}
    35  		// short-circuit--we were called with an existing directory and chown was requested
    36  		return nil
    37  	} else if err == nil {
    38  		// nothing to do; directory path fully exists already and chown was NOT requested
    39  		return nil
    40  	}
    41  
    42  	if mkAll {
    43  		// walk back to "/" looking for directories which do not exist
    44  		// and add them to the paths array for chown after creation
    45  		dirPath := path
    46  		for {
    47  			dirPath = filepath.Dir(dirPath)
    48  			if dirPath == "/" {
    49  				break
    50  			}
    51  			if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
    52  				paths = append(paths, dirPath)
    53  			}
    54  		}
    55  		if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
    56  			return err
    57  		}
    58  	} else {
    59  		if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
    60  			return err
    61  		}
    62  	}
    63  	// even if it existed, we will chown the requested path + any subpaths that
    64  	// didn't exist when we called MkdirAll
    65  	for _, pathComponent := range paths {
    66  		if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil {
    67  			return err
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  // CanAccess takes a valid (existing) directory and a uid, gid pair and determines
    74  // if that uid, gid pair has access (execute bit) to the directory
    75  func CanAccess(path string, uid, gid int) bool {
    76  	statInfo, err := system.Stat(path)
    77  	if err != nil {
    78  		return false
    79  	}
    80  	fileMode := os.FileMode(statInfo.Mode())
    81  	permBits := fileMode.Perm()
    82  	return accessible(statInfo.UID() == uint32(uid),
    83  		statInfo.GID() == uint32(gid), permBits)
    84  }
    85  
    86  func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
    87  	if isOwner && (perms&0100 == 0100) {
    88  		return true
    89  	}
    90  	if isGroup && (perms&0010 == 0010) {
    91  		return true
    92  	}
    93  	if perms&0001 == 0001 {
    94  		return true
    95  	}
    96  	return false
    97  }
    98  
    99  // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
   100  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   101  func LookupUser(username string) (user.User, error) {
   102  	// first try a local system files lookup using existing capabilities
   103  	usr, err := user.LookupUser(username)
   104  	if err == nil {
   105  		return usr, nil
   106  	}
   107  	// local files lookup failed; attempt to call `getent` to query configured passwd dbs
   108  	usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
   109  	if err != nil {
   110  		return user.User{}, err
   111  	}
   112  	return usr, nil
   113  }
   114  
   115  // LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
   116  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   117  func LookupUID(uid int) (user.User, error) {
   118  	// first try a local system files lookup using existing capabilities
   119  	usr, err := user.LookupUid(uid)
   120  	if err == nil {
   121  		return usr, nil
   122  	}
   123  	// local files lookup failed; attempt to call `getent` to query configured passwd dbs
   124  	return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
   125  }
   126  
   127  func getentUser(args string) (user.User, error) {
   128  	reader, err := callGetent(args)
   129  	if err != nil {
   130  		return user.User{}, err
   131  	}
   132  	users, err := user.ParsePasswd(reader)
   133  	if err != nil {
   134  		return user.User{}, err
   135  	}
   136  	if len(users) == 0 {
   137  		return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
   138  	}
   139  	return users[0], nil
   140  }
   141  
   142  // LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
   143  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   144  func LookupGroup(groupname string) (user.Group, error) {
   145  	// first try a local system files lookup using existing capabilities
   146  	group, err := user.LookupGroup(groupname)
   147  	if err == nil {
   148  		return group, nil
   149  	}
   150  	// local files lookup failed; attempt to call `getent` to query configured group dbs
   151  	return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
   152  }
   153  
   154  // LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
   155  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   156  func LookupGID(gid int) (user.Group, error) {
   157  	// first try a local system files lookup using existing capabilities
   158  	group, err := user.LookupGid(gid)
   159  	if err == nil {
   160  		return group, nil
   161  	}
   162  	// local files lookup failed; attempt to call `getent` to query configured group dbs
   163  	return getentGroup(fmt.Sprintf("%s %d", "group", gid))
   164  }
   165  
   166  func getentGroup(args string) (user.Group, error) {
   167  	reader, err := callGetent(args)
   168  	if err != nil {
   169  		return user.Group{}, err
   170  	}
   171  	groups, err := user.ParseGroup(reader)
   172  	if err != nil {
   173  		return user.Group{}, err
   174  	}
   175  	if len(groups) == 0 {
   176  		return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
   177  	}
   178  	return groups[0], nil
   179  }
   180  
   181  func callGetent(args string) (io.Reader, error) {
   182  	entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
   183  	// if no `getent` command on host, can't do anything else
   184  	if getentCmd == "" {
   185  		return nil, fmt.Errorf("")
   186  	}
   187  	out, err := execCmd(getentCmd, args)
   188  	if err != nil {
   189  		exitCode, errC := system.GetExitCode(err)
   190  		if errC != nil {
   191  			return nil, err
   192  		}
   193  		switch exitCode {
   194  		case 1:
   195  			return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
   196  		case 2:
   197  			terms := strings.Split(args, " ")
   198  			return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
   199  		case 3:
   200  			return nil, fmt.Errorf("getent database doesn't support enumeration")
   201  		default:
   202  			return nil, err
   203  		}
   204  
   205  	}
   206  	return bytes.NewReader(out), nil
   207  }