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