github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/stage1_fly/run/main.go (about)

     1  // Copyright 2015 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 main
    16  
    17  import (
    18  	"bufio"
    19  	"errors"
    20  	"flag"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"syscall"
    27  
    28  	"github.com/appc/spec/schema"
    29  	"github.com/appc/spec/schema/types"
    30  	"github.com/hashicorp/errwrap"
    31  
    32  	"github.com/coreos/rkt/common"
    33  	rktlog "github.com/coreos/rkt/pkg/log"
    34  	"github.com/coreos/rkt/pkg/sys"
    35  	stage1common "github.com/coreos/rkt/stage1/common"
    36  	stage1commontypes "github.com/coreos/rkt/stage1/common/types"
    37  )
    38  
    39  const (
    40  	flavor = "fly"
    41  )
    42  
    43  type flyMount struct {
    44  	HostPath         string
    45  	TargetPrefixPath string
    46  	RelTargetPath    string
    47  	Fs               string
    48  	Flags            uintptr
    49  }
    50  
    51  type volumeMountTuple struct {
    52  	V types.Volume
    53  	M schema.Mount
    54  }
    55  
    56  var (
    57  	debug bool
    58  
    59  	discardNetlist common.NetList
    60  	discardBool    bool
    61  	discardString  string
    62  
    63  	log  *rktlog.Logger
    64  	diag *rktlog.Logger
    65  )
    66  
    67  func getHostMounts() (map[string]struct{}, error) {
    68  	hostMounts := map[string]struct{}{}
    69  
    70  	mi, err := os.Open("/proc/self/mountinfo")
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	defer mi.Close()
    75  
    76  	sc := bufio.NewScanner(mi)
    77  	for sc.Scan() {
    78  		var (
    79  			discard    string
    80  			mountPoint string
    81  		)
    82  
    83  		_, err := fmt.Sscanf(sc.Text(),
    84  			"%s %s %s %s %s",
    85  			&discard, &discard, &discard, &discard, &mountPoint,
    86  		)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  
    91  		hostMounts[mountPoint] = struct{}{}
    92  	}
    93  	if sc.Err() != nil {
    94  		return nil, errwrap.Wrap(errors.New("problem parsing mountinfo"), sc.Err())
    95  	}
    96  	return hostMounts, nil
    97  }
    98  
    99  func init() {
   100  	flag.BoolVar(&debug, "debug", false, "Run in debug mode")
   101  
   102  	// The following flags need to be supported by stage1 according to
   103  	// https://github.com/coreos/rkt/blob/master/Documentation/devel/stage1-implementors-guide.md
   104  	// TODO: either implement functionality or give not implemented warnings
   105  	flag.Var(&discardNetlist, "net", "Setup networking")
   106  	flag.BoolVar(&discardBool, "interactive", true, "The pod is interactive")
   107  	flag.StringVar(&discardString, "mds-token", "", "MDS auth token")
   108  	flag.StringVar(&discardString, "local-config", common.DefaultLocalConfigDir, "Local config path")
   109  }
   110  
   111  func evaluateMounts(rfs string, app string, p *stage1commontypes.Pod) ([]flyMount, error) {
   112  	imApp := p.Images[app].App
   113  	namedVolumeMounts := map[types.ACName]volumeMountTuple{}
   114  
   115  	for _, m := range p.Manifest.Apps[0].Mounts {
   116  		_, exists := namedVolumeMounts[m.Volume]
   117  		if exists {
   118  			return nil, fmt.Errorf("duplicate mount given: %q", m.Volume)
   119  		}
   120  		namedVolumeMounts[m.Volume] = volumeMountTuple{M: m}
   121  		diag.Printf("adding %+v", namedVolumeMounts[m.Volume])
   122  	}
   123  
   124  	// Merge command-line Mounts with ImageManifest's MountPoints
   125  	for _, mp := range imApp.MountPoints {
   126  		tuple, exists := namedVolumeMounts[mp.Name]
   127  		switch {
   128  		case exists && tuple.M.Path != mp.Path:
   129  			return nil, fmt.Errorf("conflicting path information from mount and mountpoint %q", mp.Name)
   130  		case !exists:
   131  			namedVolumeMounts[mp.Name] = volumeMountTuple{M: schema.Mount{Volume: mp.Name, Path: mp.Path}}
   132  			diag.Printf("adding %+v", namedVolumeMounts[mp.Name])
   133  		}
   134  	}
   135  
   136  	// Insert the command-line Volumes
   137  	for _, v := range p.Manifest.Volumes {
   138  		// Check if we have a mount for this volume
   139  		tuple, exists := namedVolumeMounts[v.Name]
   140  		if !exists {
   141  			return nil, fmt.Errorf("missing mount for volume %q", v.Name)
   142  		} else if tuple.M.Volume != v.Name {
   143  			// assertion regarding the implementation, should never happen
   144  			return nil, fmt.Errorf("mismatched volume:mount pair: %q != %q", v.Name, tuple.M.Volume)
   145  		}
   146  		namedVolumeMounts[v.Name] = volumeMountTuple{V: v, M: tuple.M}
   147  		diag.Printf("adding %+v", namedVolumeMounts[v.Name])
   148  	}
   149  
   150  	// Merge command-line Volumes with ImageManifest's MountPoints
   151  	for _, mp := range imApp.MountPoints {
   152  		// Check if we have a volume for this mountpoint
   153  		tuple, exists := namedVolumeMounts[mp.Name]
   154  		if !exists || tuple.V.Name == "" {
   155  			return nil, fmt.Errorf("missing volume for mountpoint %q", mp.Name)
   156  		}
   157  
   158  		// If empty, fill in ReadOnly bit
   159  		if tuple.V.ReadOnly == nil {
   160  			v := tuple.V
   161  			v.ReadOnly = &mp.ReadOnly
   162  			namedVolumeMounts[mp.Name] = volumeMountTuple{M: tuple.M, V: v}
   163  			diag.Printf("adding %+v", namedVolumeMounts[mp.Name])
   164  		}
   165  	}
   166  
   167  	// Gather host mounts which we make MS_SHARED if passed as a volume source
   168  	hostMounts, err := getHostMounts()
   169  	if err != nil {
   170  		return nil, errwrap.Wrap(errors.New("can't gather host mounts"), err)
   171  	}
   172  
   173  	argFlyMounts := []flyMount{}
   174  	var flags uintptr = syscall.MS_BIND // TODO: allow optional | syscall.MS_REC
   175  	for _, tuple := range namedVolumeMounts {
   176  		if _, isHostMount := hostMounts[tuple.V.Source]; isHostMount {
   177  			// Mark the host mount as SHARED so the container's changes to the mount are propagated to the host
   178  			argFlyMounts = append(argFlyMounts,
   179  				flyMount{"", "", tuple.V.Source, "none", syscall.MS_REC | syscall.MS_SHARED},
   180  			)
   181  		}
   182  		argFlyMounts = append(argFlyMounts,
   183  			flyMount{tuple.V.Source, rfs, tuple.M.Path, "none", flags},
   184  		)
   185  
   186  		if tuple.V.ReadOnly != nil && *tuple.V.ReadOnly {
   187  			argFlyMounts = append(argFlyMounts,
   188  				flyMount{"", rfs, tuple.M.Path, "none", flags | syscall.MS_REMOUNT | syscall.MS_RDONLY},
   189  			)
   190  		}
   191  	}
   192  	return argFlyMounts, nil
   193  }
   194  
   195  func stage1() int {
   196  	uuid, err := types.NewUUID(flag.Arg(0))
   197  	if err != nil {
   198  		log.Print("UUID is missing or malformed\n")
   199  		return 1
   200  	}
   201  
   202  	root := "."
   203  	p, err := stage1commontypes.LoadPod(root, uuid)
   204  	if err != nil {
   205  		log.PrintE("can't load pod", err)
   206  		return 1
   207  	}
   208  
   209  	if len(p.Manifest.Apps) != 1 {
   210  		log.Printf("flavor %q only supports 1 application per Pod for now", flavor)
   211  		return 1
   212  	}
   213  
   214  	lfd, err := common.GetRktLockFD()
   215  	if err != nil {
   216  		log.PrintE("can't get rkt lock fd", err)
   217  		return 1
   218  	}
   219  
   220  	// set close-on-exec flag on RKT_LOCK_FD so it gets correctly closed after execution is finished
   221  	if err := sys.CloseOnExec(lfd, true); err != nil {
   222  		log.PrintE("can't set FD_CLOEXEC on rkt lock", err)
   223  		return 1
   224  	}
   225  
   226  	env := []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
   227  	for _, e := range p.Manifest.Apps[0].App.Environment {
   228  		env = append(env, e.Name+"="+e.Value)
   229  	}
   230  
   231  	args := p.Manifest.Apps[0].App.Exec
   232  	rfs := filepath.Join(common.AppPath(p.Root, p.Manifest.Apps[0].Name), "rootfs")
   233  
   234  	argFlyMounts, err := evaluateMounts(rfs, string(p.Manifest.Apps[0].Name), p)
   235  	if err != nil {
   236  		log.PrintE("can't evaluate mounts", err)
   237  		return 1
   238  	}
   239  
   240  	effectiveMounts := append(
   241  		[]flyMount{
   242  			{"", "", "/dev", "none", syscall.MS_REC | syscall.MS_SHARED},
   243  			{"/dev", rfs, "/dev", "none", syscall.MS_BIND | syscall.MS_REC},
   244  
   245  			{"", "", "/proc", "none", syscall.MS_REC | syscall.MS_SHARED},
   246  			{"/proc", rfs, "/proc", "none", syscall.MS_BIND | syscall.MS_REC},
   247  
   248  			{"", "", "/sys", "none", syscall.MS_REC | syscall.MS_SHARED},
   249  			{"/sys", rfs, "/sys", "none", syscall.MS_BIND | syscall.MS_REC},
   250  
   251  			{"tmpfs", rfs, "/tmp", "tmpfs", 0},
   252  		},
   253  		argFlyMounts...,
   254  	)
   255  
   256  	for _, mount := range effectiveMounts {
   257  		var (
   258  			err            error
   259  			hostPathInfo   os.FileInfo
   260  			targetPathInfo os.FileInfo
   261  		)
   262  
   263  		if strings.HasPrefix(mount.HostPath, "/") {
   264  			if hostPathInfo, err = os.Stat(mount.HostPath); err != nil {
   265  				log.PrintE(fmt.Sprintf("stat of host directory %s", mount.HostPath), err)
   266  				return 1
   267  			}
   268  		} else {
   269  			hostPathInfo = nil
   270  		}
   271  
   272  		absTargetPath := filepath.Join(mount.TargetPrefixPath, mount.RelTargetPath)
   273  		if targetPathInfo, err = os.Stat(absTargetPath); err != nil && !os.IsNotExist(err) {
   274  			log.PrintE(fmt.Sprintf("stat of target directory %s", absTargetPath), err)
   275  			return 1
   276  		}
   277  
   278  		switch {
   279  		case targetPathInfo == nil:
   280  			absTargetPathParent, _ := filepath.Split(absTargetPath)
   281  			if err := os.MkdirAll(absTargetPathParent, 0700); err != nil {
   282  				log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err)
   283  				return 1
   284  			}
   285  			switch {
   286  			case hostPathInfo == nil || hostPathInfo.IsDir():
   287  				if err := os.Mkdir(absTargetPath, 0700); err != nil {
   288  					log.PrintE(fmt.Sprintf("can't create directory %q", absTargetPath), err)
   289  					return 1
   290  				}
   291  			case !hostPathInfo.IsDir():
   292  				file, err := os.OpenFile(absTargetPath, os.O_CREATE, 0700)
   293  				if err != nil {
   294  					log.PrintE(fmt.Sprintf("can't create file %q", absTargetPath), err)
   295  					return 1
   296  				}
   297  				file.Close()
   298  			}
   299  		case hostPathInfo != nil:
   300  			switch {
   301  			case hostPathInfo.IsDir() && !targetPathInfo.IsDir():
   302  				log.Printf("can't mount because %q is a directory while %q is not", mount.HostPath, absTargetPath)
   303  				return 1
   304  			case !hostPathInfo.IsDir() && targetPathInfo.IsDir():
   305  				log.Printf("can't mount because %q is not a directory while %q is", mount.HostPath, absTargetPath)
   306  				return 1
   307  			}
   308  		}
   309  
   310  		if err := syscall.Mount(mount.HostPath, absTargetPath, mount.Fs, mount.Flags, ""); err != nil {
   311  			log.PrintE(fmt.Sprintf("can't mount %q on %q with flags %v", mount.HostPath, absTargetPath, mount.Flags), err)
   312  			return 1
   313  		}
   314  	}
   315  
   316  	if err = stage1common.WritePpid(os.Getpid()); err != nil {
   317  		log.Error(err)
   318  		return 4
   319  	}
   320  
   321  	diag.Printf("chroot to %q", rfs)
   322  	if err := syscall.Chroot(rfs); err != nil {
   323  		log.PrintE("can't chroot", err)
   324  		return 1
   325  	}
   326  
   327  	if err := os.Chdir("/"); err != nil {
   328  		log.PrintE("can't change to root new directory", err)
   329  		return 1
   330  	}
   331  
   332  	diag.Printf("execing %q in %q", args, rfs)
   333  	err = stage1common.WithClearedCloExec(lfd, func() error {
   334  		return syscall.Exec(args[0], args, env)
   335  	})
   336  	if err != nil {
   337  		log.PrintE(fmt.Sprintf("can't execute %q", args[0]), err)
   338  		return 7
   339  	}
   340  
   341  	return 0
   342  }
   343  
   344  func main() {
   345  	flag.Parse()
   346  
   347  	log, diag, _ = rktlog.NewLogSet("run", debug)
   348  	if !debug {
   349  		log.SetOutput(ioutil.Discard)
   350  	}
   351  
   352  	// move code into stage1() helper so defered fns get run
   353  	os.Exit(stage1())
   354  }