github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/worker/runtime/rootfs_manager.go (about)

     1  package runtime
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/opencontainers/runtime-spec/specs-go"
    12  )
    13  
    14  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . RootfsManager
    15  
    16  type InvalidUidError struct {
    17  	UID string
    18  }
    19  type InvalidGidError struct {
    20  	GID string
    21  }
    22  
    23  func (e InvalidUidError) Error() string {
    24  	return fmt.Sprintf("invalid uid: %s", e.UID)
    25  }
    26  
    27  func (e InvalidGidError) Error() string {
    28  	return fmt.Sprintf("invalid gid: %s", e.GID)
    29  }
    30  
    31  const (
    32  	DefaultUid = 0
    33  	DefaultGid = 0
    34  )
    35  
    36  // RootfsManager is responsible for mutating and reading from the rootfs of a
    37  // container.
    38  //
    39  type RootfsManager interface {
    40  	// SetupCwd mutates the root filesystem to guarantee the presence of a
    41  	// directory to be used as `cwd`.
    42  	//
    43  	SetupCwd(rootfsPath string, cwd string) (err error)
    44  
    45  	// LookupUser scans the /etc/passwd file from the root filesystem for the
    46  	// UID and GID of the specified username.
    47  	//
    48  	LookupUser(rootfsPath string, username string) (specs.User, bool, error)
    49  }
    50  
    51  // RootfsManagerOpt defines a functional option that when applied, modifies the
    52  // configuration of a rootfsManager.
    53  //
    54  type RootfsManagerOpt func(m *rootfsManager)
    55  
    56  // WithMkdirAll configures the function to be used for creating directories
    57  // recursively.
    58  //
    59  func WithMkdirAll(f func(path string, mode os.FileMode) error) RootfsManagerOpt {
    60  	return func(m *rootfsManager) {
    61  		m.mkdirall = f
    62  	}
    63  }
    64  
    65  type rootfsManager struct {
    66  	mkdirall func(name string, mode os.FileMode) error
    67  }
    68  
    69  var _ RootfsManager = (*rootfsManager)(nil)
    70  
    71  // NewRootfsManager instantiates a rootfsManager
    72  //
    73  func NewRootfsManager(opts ...RootfsManagerOpt) *rootfsManager {
    74  	m := &rootfsManager{
    75  		mkdirall: os.MkdirAll,
    76  	}
    77  
    78  	for _, opt := range opts {
    79  		opt(m)
    80  	}
    81  
    82  	return m
    83  }
    84  
    85  func (r rootfsManager) SetupCwd(rootfsPath string, cwd string) error {
    86  	abs := filepath.Join(rootfsPath, cwd)
    87  
    88  	_, err := os.Stat(abs)
    89  	if err == nil { // exists
    90  		return nil
    91  	}
    92  
    93  	err = r.mkdirall(abs, 0777)
    94  	if err != nil {
    95  		return fmt.Errorf("mkdir: %w", err)
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  // Mostly copied from Go's `os/user` package
   102  // https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/os/user/lookup_unix.go#L35
   103  func (r rootfsManager) LookupUser(rootfsPath string, username string) (specs.User, bool, error) {
   104  	path := filepath.Join(rootfsPath, "etc", "passwd")
   105  	file, err := os.Open(path)
   106  
   107  	// follow what runc and garden does - if /etc/passwd doesn't exist but the username is "root",
   108  	// assume it means 0:0
   109  	if os.IsNotExist(err) && username == "root" {
   110  		return specs.User{UID: DefaultUid, GID: DefaultGid}, true, nil
   111  	}
   112  	if err != nil {
   113  		return specs.User{}, false, err
   114  	}
   115  	defer file.Close()
   116  
   117  	bs := bufio.NewScanner(file)
   118  	for bs.Scan() {
   119  		line := bs.Text()
   120  
   121  		// There's no spec for /etc/passwd or /etc/group, but we try to follow
   122  		// the same rules as the glibc parser, which allows comments and blank
   123  		// space at the beginning of a line.
   124  		line = strings.TrimSpace(line)
   125  		if len(line) == 0 || line[0] == '#' {
   126  			continue
   127  		}
   128  
   129  		parts := strings.Split(line, ":")
   130  		if len(parts) != 7 {
   131  			continue
   132  		}
   133  		if parts[0] != username {
   134  			continue
   135  		}
   136  		var (
   137  			uid int
   138  			gid int
   139  		)
   140  		if uid, err = strconv.Atoi(parts[2]); err != nil {
   141  			return specs.User{}, false, InvalidUidError{UID: parts[2]}
   142  		}
   143  		if gid, err = strconv.Atoi(parts[3]); err != nil {
   144  			return specs.User{}, false, InvalidGidError{GID: parts[3]}
   145  		}
   146  		return specs.User{UID: uint32(uid), GID: uint32(gid)}, true, nil
   147  	}
   148  	return specs.User{}, false, bs.Err()
   149  }