github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/idtools/idtools_unix.go (about)

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