github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/common/common.go (about)

     1  // Copyright 2014 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package common defines values shared by different parts
    16  // of rkt (e.g. stage0 and stage1)
    17  package common
    18  
    19  import (
    20  	"bufio"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"runtime"
    28  	"strconv"
    29  	"strings"
    30  	"syscall"
    31  	"unsafe"
    32  
    33  	"github.com/appc/spec/aci"
    34  	"github.com/appc/spec/schema/types"
    35  	"github.com/hashicorp/errwrap"
    36  
    37  	"github.com/rkt/rkt/pkg/fileutil"
    38  )
    39  
    40  const (
    41  	sharedVolumesDir = "/sharedVolumes"
    42  	SharedVolumePerm = os.FileMode(0755)
    43  	stage1Dir        = "/stage1"
    44  	stage2Dir        = "/opt/stage2"
    45  	AppsInfoDir      = "/appsinfo"
    46  
    47  	EnvLockFd                    = "RKT_LOCK_FD"
    48  	EnvSELinuxContext            = "RKT_SELINUX_CONTEXT"
    49  	EnvSELinuxMountContext       = "RKT_SELINUX_MOUNT_CONTEXT"
    50  	Stage1TreeStoreIDFilename    = "stage1TreeStoreID"
    51  	AppTreeStoreIDFilename       = "treeStoreID"
    52  	OverlayPreparedFilename      = "overlay-prepared"
    53  	PrivateUsersPreparedFilename = "private-users-prepared"
    54  
    55  	PrepareLock = "prepareLock"
    56  
    57  	MetadataServicePort    = 18112
    58  	MetadataServiceRegSock = "/run/rkt/metadata-svc.sock"
    59  
    60  	APIServiceListenAddr = "localhost:15441"
    61  
    62  	DefaultLocalConfigDir  = "/etc/rkt"
    63  	DefaultSystemConfigDir = "/usr/lib/rkt"
    64  
    65  	// Default perm bits for the regular files
    66  	// within the stage1 directory. (e.g. image manifest,
    67  	// pod manifest, stage1ID, etc).
    68  	DefaultRegularFilePerm = os.FileMode(0640)
    69  
    70  	// Default perm bits for the regular directories
    71  	// within the stage1 directory.
    72  	DefaultRegularDirPerm = os.FileMode(0750)
    73  
    74  	// Enter command for crossing entrypoints.
    75  	CrossingEnterCmd = "RKT_STAGE1_ENTERCMD"
    76  	// Stage1 (PID) to enter, used by crossing entrypoints.
    77  	CrossingEnterPID = "RKT_STAGE1_ENTERPID"
    78  	// Stage2 (application name) to enter, optionally used by crossing entrypoints.
    79  	CrossingEnterApp = "RKT_STAGE1_ENTERAPP"
    80  )
    81  
    82  const (
    83  	FsMagicAUFS = 0x61756673 // https://goo.gl/CBwx43
    84  	FsMagicZFS  = 0x2FC12FC1 // https://goo.gl/xTvzO5
    85  )
    86  
    87  // ErrOverlayUnsupported is the error determining whether OverlayFS is supported.
    88  type ErrOverlayUnsupported string
    89  
    90  func (e ErrOverlayUnsupported) Error() string {
    91  	return string(e)
    92  }
    93  
    94  // Stage1ImagePath returns the path where the stage1 app image (unpacked ACI) is rooted,
    95  // (i.e. where its contents are extracted during stage0).
    96  func Stage1ImagePath(root string) string {
    97  	return filepath.Join(root, stage1Dir)
    98  }
    99  
   100  // Stage1RootfsPath returns the path to the stage1 rootfs
   101  func Stage1RootfsPath(root string) string {
   102  	return filepath.Join(Stage1ImagePath(root), aci.RootfsDir)
   103  }
   104  
   105  // Stage1ManifestPath returns the path to the stage1's manifest file inside the expanded ACI.
   106  func Stage1ManifestPath(root string) string {
   107  	return filepath.Join(Stage1ImagePath(root), aci.ManifestFile)
   108  }
   109  
   110  // PodManifestPath returns the path in root to the Pod Manifest
   111  func PodManifestPath(root string) string {
   112  	return filepath.Join(root, "pod")
   113  }
   114  
   115  // PodCreatedPath returns the path in root to the Pod Created file used to
   116  // denote the time of creation.
   117  func PodCreatedPath(root string) string {
   118  	return filepath.Join(root, "pod-created")
   119  }
   120  
   121  // PodManifestLockPath returns the path in root to the Pod Manifest lock file.
   122  // This must be different from the PodManifestPath since mutations on the pod manifest file
   123  // happen by overwriting the original file.
   124  func PodManifestLockPath(root string) string {
   125  	return filepath.Join(root, "pod.lck")
   126  }
   127  
   128  // AppsStatusesPathFromStage1Rootfs returns the path of the status dir for all apps.
   129  // It receives the stage1 rootfs as parameter instead of the pod root.
   130  func AppsStatusesPathFromStage1Rootfs(rootfs string) string {
   131  	return filepath.Join(rootfs, "/rkt/status")
   132  }
   133  
   134  // AppsStatusesPath returns the path of the status dir for all apps.
   135  func AppsStatusesPath(root string) string {
   136  	return AppsStatusesPathFromStage1Rootfs(Stage1RootfsPath(root))
   137  }
   138  
   139  // AppStatusPath returns the path of the status file of an app.
   140  func AppStatusPath(root, appName string) string {
   141  	return filepath.Join(AppsStatusesPath(root), appName)
   142  }
   143  
   144  // AppStatusPathFromStage1Rootfs returns the path of the status file of an app.
   145  // It receives the stage1 rootfs as parameter instead of the pod root.
   146  func AppStatusPathFromStage1Rootfs(rootfs, appName string) string {
   147  	return filepath.Join(AppsStatusesPathFromStage1Rootfs(rootfs), appName)
   148  }
   149  
   150  // AppCreatedPath returns the path of the ${appname}-created file, which is used to record
   151  // the creation timestamp of the app.
   152  func AppCreatedPath(root, appName string) string {
   153  	return filepath.Join(AppsStatusesPath(root), fmt.Sprintf("%s-created", appName))
   154  }
   155  
   156  // AppCreatedPathFromStage1Rootfs returns the path of the ${appname}-created file,
   157  // which is used to record the creation timestamp of the app.
   158  // It receives the stage1 rootfs as parameter instead of the pod root.
   159  func AppCreatedPathFromStage1Rootfs(rootfs, appName string) string {
   160  	return filepath.Join(AppsStatusesPathFromStage1Rootfs(rootfs), fmt.Sprintf("%s-created", appName))
   161  }
   162  
   163  // AppStartedPath returns the path of the ${appname}-started file, which is used to record
   164  // the start timestamp of the app.
   165  func AppStartedPath(root, appName string) string {
   166  	return filepath.Join(AppsStatusesPath(root), fmt.Sprintf("%s-started", appName))
   167  }
   168  
   169  // AppStartedPathFromStage1Rootfs returns the path of the ${appname}-started file, which is used to record
   170  // the start timestamp of the app.
   171  // It receives the stage1 rootfs as parameter instead of the pod root.
   172  func AppStartedPathFromStage1Rootfs(rootfs, appName string) string {
   173  	return filepath.Join(AppsStatusesPathFromStage1Rootfs(rootfs), fmt.Sprintf("%s-started", appName))
   174  }
   175  
   176  // AppsPath returns the path where the apps within a pod live.
   177  func AppsPath(root string) string {
   178  	return filepath.Join(Stage1RootfsPath(root), stage2Dir)
   179  }
   180  
   181  // AppPath returns the path to an app's rootfs.
   182  func AppPath(root string, appName types.ACName) string {
   183  	return filepath.Join(AppsPath(root), appName.String())
   184  }
   185  
   186  // AppRootfsPath returns the path to an app's rootfs.
   187  func AppRootfsPath(root string, appName types.ACName) string {
   188  	return filepath.Join(AppPath(root, appName), aci.RootfsDir)
   189  }
   190  
   191  // RelAppPath returns the path of an app relative to the stage1 chroot.
   192  func RelAppPath(appName types.ACName) string {
   193  	return filepath.Join(stage2Dir, appName.String())
   194  }
   195  
   196  // RelAppRootfsPath returns the path of an app's rootfs relative to the stage1 chroot.
   197  func RelAppRootfsPath(appName types.ACName) string {
   198  	return filepath.Join(RelAppPath(appName), aci.RootfsDir)
   199  }
   200  
   201  // ImageManifestPath returns the path to the app's manifest file of a pod.
   202  func ImageManifestPath(root string, appName types.ACName) string {
   203  	return filepath.Join(AppPath(root, appName), aci.ManifestFile)
   204  }
   205  
   206  // AppsInfoPath returns the path to the appsinfo directory of a pod.
   207  func AppsInfoPath(root string) string {
   208  	return filepath.Join(root, AppsInfoDir)
   209  }
   210  
   211  // AppInfoPath returns the path to the app's appsinfo directory of a pod.
   212  func AppInfoPath(root string, appName types.ACName) string {
   213  	return filepath.Join(AppsInfoPath(root), appName.String())
   214  }
   215  
   216  // AppTreeStoreIDPath returns the path to the app's treeStoreID file of a pod.
   217  func AppTreeStoreIDPath(root string, appName types.ACName) string {
   218  	return filepath.Join(AppInfoPath(root, appName), AppTreeStoreIDFilename)
   219  }
   220  
   221  // AppImageManifestPath returns the path to the app's ImageManifest file
   222  func AppImageManifestPath(root string, appName types.ACName) string {
   223  	return filepath.Join(AppInfoPath(root, appName), aci.ManifestFile)
   224  }
   225  
   226  // SharedVolumesPath returns the path to the shared (empty) volumes of a pod.
   227  func SharedVolumesPath(root string) string {
   228  	return filepath.Join(root, sharedVolumesDir)
   229  }
   230  
   231  // CreateSharedVolumesPath ensures the sharedVolumePath for the pod root passed
   232  // in exists. It returns the shared volume path or an error.
   233  func CreateSharedVolumesPath(root string) (string, error) {
   234  	sharedVolPath := SharedVolumesPath(root)
   235  
   236  	if err := os.MkdirAll(sharedVolPath, SharedVolumePerm); err != nil {
   237  		return "", errwrap.Wrap(errors.New("could not create shared volumes directory"), err)
   238  	}
   239  	// In case it already existed and we didn't make it, ensure permissions are
   240  	// what the caller expects them to be.
   241  	if err := os.Chmod(sharedVolPath, SharedVolumePerm); err != nil {
   242  		return "", errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err)
   243  	}
   244  
   245  	return sharedVolPath, nil
   246  }
   247  
   248  // MetadataServicePublicURL returns the public URL used to host the metadata service
   249  func MetadataServicePublicURL(ip net.IP, token string) string {
   250  	return fmt.Sprintf("http://%v:%v/%v", ip, MetadataServicePort, token)
   251  }
   252  
   253  func GetRktLockFD() (int, error) {
   254  	if v := os.Getenv(EnvLockFd); v != "" {
   255  		fd, err := strconv.ParseUint(v, 10, 32)
   256  		if err != nil {
   257  			return -1, err
   258  		}
   259  		return int(fd), nil
   260  	}
   261  	return -1, fmt.Errorf("%v env var is not set", EnvLockFd)
   262  }
   263  
   264  // SupportsUserNS returns whether the kernel has CONFIG_USER_NS set
   265  func SupportsUserNS() bool {
   266  	if _, err := os.Stat("/proc/self/uid_map"); err == nil {
   267  		return true
   268  	}
   269  
   270  	return false
   271  }
   272  
   273  // NetList implements the flag.Value interface to allow specification of --net with and without values
   274  // Example: --net="all,net1:k1=v1;k2=v2,net2:l1=w1"
   275  type NetList struct {
   276  	mapping map[string]string
   277  }
   278  
   279  func (l *NetList) String() string {
   280  	return strings.Join(l.Strings(), ",")
   281  }
   282  
   283  func (l *NetList) Set(value string) error {
   284  	if l.mapping == nil {
   285  		l.mapping = make(map[string]string)
   286  	}
   287  	for _, s := range strings.Split(value, ",") {
   288  		netArgsPair := strings.Split(s, ":")
   289  		netName := netArgsPair[0]
   290  
   291  		if netName == "" {
   292  			return fmt.Errorf("netname must not be empty")
   293  		}
   294  
   295  		if _, duplicate := l.mapping[netName]; duplicate {
   296  			return fmt.Errorf("found duplicate netname %q", netName)
   297  		}
   298  
   299  		switch {
   300  		case len(netArgsPair) == 1:
   301  			l.mapping[netName] = ""
   302  		case len(netArgsPair) == 2:
   303  			if netName == "all" ||
   304  				netName == "host" {
   305  				return fmt.Errorf("arguments are not supported by special netname %q", netName)
   306  			}
   307  			l.mapping[netName] = netArgsPair[1]
   308  		case len(netArgsPair) > 2:
   309  			return fmt.Errorf("network %q provided with invalid arguments: %v", netName, netArgsPair[1:])
   310  		default:
   311  			return fmt.Errorf("unexpected case when processing network %q", s)
   312  		}
   313  	}
   314  	return nil
   315  }
   316  
   317  func (l *NetList) Type() string {
   318  	return "netList"
   319  }
   320  
   321  func (l *NetList) Strings() []string {
   322  	if len(l.mapping) == 0 {
   323  		return []string{"default"}
   324  	}
   325  
   326  	var list []string
   327  	for k, v := range l.mapping {
   328  		if v == "" {
   329  			list = append(list, k)
   330  		} else {
   331  			list = append(list, fmt.Sprintf("%s:%s", k, v))
   332  		}
   333  	}
   334  	return list
   335  }
   336  
   337  func (l *NetList) StringsOnlyNames() (list []string) {
   338  	for k := range l.mapping {
   339  		list = append(list, k)
   340  	}
   341  
   342  	return
   343  }
   344  
   345  // Check if host networking has been requested
   346  func (l *NetList) Host() bool {
   347  	return l.Specific("host")
   348  }
   349  
   350  // Check if 'none' (loopback only) networking has been requested
   351  func (l *NetList) None() bool {
   352  	return l.Specific("none")
   353  }
   354  
   355  // Check if the container needs to be put in a separate network namespace
   356  func (l *NetList) Contained() bool {
   357  	return !l.Host() && len(l.mapping) > 0
   358  }
   359  
   360  func (l *NetList) Specific(net string) bool {
   361  	_, exists := l.mapping[net]
   362  	return exists
   363  }
   364  
   365  func (l *NetList) SpecificArgs(net string) string {
   366  	return l.mapping[net]
   367  }
   368  
   369  func (l *NetList) All() bool {
   370  	return l.Specific("all")
   371  }
   372  
   373  // LookupPath search for bin in paths. If found, it returns its absolute path,
   374  // if not, an error
   375  func LookupPath(bin string, paths string) (string, error) {
   376  	pathsArr := filepath.SplitList(paths)
   377  	for _, path := range pathsArr {
   378  		binPath := filepath.Join(path, bin)
   379  		binAbsPath, err := filepath.Abs(binPath)
   380  		if err != nil {
   381  			return "", fmt.Errorf("unable to find absolute path for %s", binPath)
   382  		}
   383  		if fileutil.IsExecutable(binAbsPath) {
   384  			return binAbsPath, nil
   385  		}
   386  	}
   387  	return "", fmt.Errorf("unable to find %q in %q", bin, paths)
   388  }
   389  
   390  // SystemdVersion parses and returns the version of a given systemd binary
   391  func SystemdVersion(systemdBinaryPath string) (int, error) {
   392  	versionBytes, err := exec.Command(systemdBinaryPath, "--version").CombinedOutput()
   393  	if err != nil {
   394  		return -1, errwrap.Wrap(fmt.Errorf("unable to probe %s version", systemdBinaryPath), err)
   395  	}
   396  	versionStr := strings.SplitN(string(versionBytes), "\n", 2)[0]
   397  	var version int
   398  	n, err := fmt.Sscanf(versionStr, "systemd %d", &version)
   399  	if err != nil || n != 1 {
   400  		return -1, fmt.Errorf("cannot parse version: %q", versionStr)
   401  	}
   402  
   403  	return version, nil
   404  }
   405  
   406  // SupportsOverlay returns whether the operating system generally supports OverlayFS,
   407  // returning an instance of ErrOverlayUnsupported which encodes the reason.
   408  // It is sufficient to check for nil if the reason is not of interest.
   409  func SupportsOverlay() error {
   410  	// ignore exec.Command error, modprobe may not be present on the system,
   411  	// or the kernel module will fail to load.
   412  	// we'll find out by reading the side effect in /proc/filesystems
   413  	_ = exec.Command("modprobe", "overlay").Run()
   414  
   415  	f, err := os.Open("/proc/filesystems")
   416  	if err != nil {
   417  		// don't use errwrap so consumers can type-check on ErrOverlayUnsupported
   418  		return ErrOverlayUnsupported(fmt.Sprintf("cannot open /proc/filesystems: %v", err))
   419  	}
   420  	defer f.Close()
   421  
   422  	s := bufio.NewScanner(f)
   423  	for s.Scan() {
   424  		if s.Text() == "nodev\toverlay" {
   425  			return nil
   426  		}
   427  	}
   428  
   429  	return ErrOverlayUnsupported("overlay entry not present in /proc/filesystems")
   430  }
   431  
   432  // PathSupportsOverlay checks whether the given path is compatible with OverlayFS.
   433  // This method also calls SupportsOverlay().
   434  //
   435  // It returns an instance of ErrOverlayUnsupported if OverlayFS is not supported
   436  // or any other error if determining overlay support failed.
   437  func PathSupportsOverlay(path string) error {
   438  	if err := SupportsOverlay(); err != nil {
   439  		// don't wrap since SupportsOverlay already returns ErrOverlayUnsupported
   440  		return err
   441  	}
   442  
   443  	var data syscall.Statfs_t
   444  	if err := syscall.Statfs(path, &data); err != nil {
   445  		return errwrap.Wrap(fmt.Errorf("cannot statfs %q", path), err)
   446  	}
   447  
   448  	switch data.Type {
   449  	case FsMagicAUFS:
   450  		return ErrOverlayUnsupported("unsupported filesystem: aufs")
   451  	case FsMagicZFS:
   452  		return ErrOverlayUnsupported("unsupported filesystem: zfs")
   453  	}
   454  
   455  	dir, err := os.OpenFile(path, syscall.O_RDONLY|syscall.O_DIRECTORY, 0755)
   456  	if err != nil {
   457  		return errwrap.Wrap(fmt.Errorf("cannot open %q", path), err)
   458  	}
   459  	defer dir.Close()
   460  
   461  	buf := make([]byte, 4096)
   462  	// ReadDirent forwards to the raw syscall getdents(3),
   463  	// passing the buffer size.
   464  	n, err := syscall.ReadDirent(int(dir.Fd()), buf)
   465  	if err != nil {
   466  		return errwrap.Wrap(fmt.Errorf("cannot read directory %q", path), err)
   467  	}
   468  
   469  	offset := 0
   470  	for offset < n {
   471  		// offset overflow cannot happen, because Reclen
   472  		// is being maintained by getdents(3), considering the buffer size.
   473  		dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[offset]))
   474  		offset += int(dirent.Reclen)
   475  
   476  		if dirent.Ino == 0 { // File absent in directory.
   477  			continue
   478  		}
   479  
   480  		if dirent.Type == syscall.DT_UNKNOWN {
   481  			return ErrOverlayUnsupported("unsupported filesystem: missing d_type support")
   482  		}
   483  	}
   484  
   485  	return nil
   486  }
   487  
   488  // RemoveEmptyLines removes empty lines from the given string
   489  // and breaks it up into a list of strings at newline characters
   490  func RemoveEmptyLines(str string) []string {
   491  	lines := make([]string, 0)
   492  
   493  	for _, v := range strings.Split(str, "\n") {
   494  		if len(v) > 0 {
   495  			lines = append(lines, v)
   496  		}
   497  	}
   498  
   499  	return lines
   500  }
   501  
   502  // GetExitStatus converts an error to an exit status. If it wasn't an exit
   503  // status != 0 it returns the same error that it was called with
   504  func GetExitStatus(err error) (int, error) {
   505  	if err == nil {
   506  		return 0, nil
   507  	}
   508  	if exiterr, ok := err.(*exec.ExitError); ok {
   509  		// the program has exited with an exit code != 0
   510  		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   511  			return status.ExitStatus(), nil
   512  		}
   513  	}
   514  	return -1, err
   515  }
   516  
   517  // GetOS returns the current ACI operating system (linux, windows etc...)
   518  func GetOS() string {
   519  	os, _ := GetOSArch()
   520  	return os
   521  }
   522  
   523  // GetArch returns the current ACI architecture.
   524  func GetArch() string {
   525  	_, arch := GetOSArch()
   526  	return arch
   527  }
   528  
   529  // reference to GOARM, needs to be globally, if in GetArch() function it resolves to zero
   530  //go:linkname goarm runtime.goarm
   531  var goarm uint8
   532  
   533  func GetOSArch() (os string, arch string) {
   534  	arch = runtime.GOARCH
   535  	flavor := ""
   536  	if arch == "arm" {
   537  		flavor = strconv.Itoa(int(goarm))
   538  	}
   539  	os, arch, _ = types.ToAppcOSArch(runtime.GOOS, arch, flavor)
   540  	return os, arch
   541  }
   542  
   543  // ImageNameToAppName converts the full name of image to an app name without special
   544  // characters - we use it as a default app name when specyfing it is optional
   545  func ImageNameToAppName(name types.ACIdentifier) (*types.ACName, error) {
   546  	parts := strings.Split(name.String(), "/")
   547  	last := parts[len(parts)-1]
   548  
   549  	sn, err := types.SanitizeACName(last)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  
   554  	return types.MustACName(sn), nil
   555  }