github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/stage1/init/common/mount.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 common
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"strconv"
    23  	"syscall"
    24  
    25  	"github.com/rkt/rkt/common"
    26  	"github.com/rkt/rkt/pkg/fileutil"
    27  	"github.com/rkt/rkt/pkg/fs"
    28  	"github.com/rkt/rkt/pkg/user"
    29  
    30  	"github.com/appc/spec/schema"
    31  	"github.com/appc/spec/schema/types"
    32  	"github.com/hashicorp/errwrap"
    33  )
    34  
    35  /*
    36   * Some common stage1 mount tasks
    37   *
    38   * TODO(cdc) De-duplicate code from stage0/gc.go
    39   */
    40  
    41  // Mount extends schema.Mount with additional rkt specific fields.
    42  type Mount struct {
    43  	schema.Mount
    44  
    45  	Volume         types.Volume
    46  	DockerImplicit bool
    47  	ReadOnly       bool
    48  }
    49  
    50  // ConvertedFromDocker determines if an app's image has been converted
    51  // from docker. This is needed because implicit docker empty volumes have
    52  // different behavior from AppC
    53  func ConvertedFromDocker(im *schema.ImageManifest) bool {
    54  	if im == nil { // nil sometimes sneaks in here due to unit tests
    55  		return false
    56  	}
    57  	ann := im.Annotations
    58  	_, ok := ann.Get("appc.io/docker/repository")
    59  	return ok
    60  }
    61  
    62  // Source computes the real volume source for a volume.
    63  // Volumes of type 'empty' use a workdir relative to podRoot
    64  func (m *Mount) Source(podRoot string) string {
    65  	switch m.Volume.Kind {
    66  	case "host":
    67  		return m.Volume.Source
    68  	case "empty":
    69  		return filepath.Join(common.SharedVolumesPath(podRoot), m.Volume.Name.String())
    70  	}
    71  	return "" // We validate in GenerateMounts that it's valid
    72  }
    73  
    74  // GenerateMounts maps MountPoint paths to volumes, returning a list of mounts,
    75  // each with a parameter indicating if it's an implicit empty volume from a
    76  // Docker image.
    77  func GenerateMounts(ra *schema.RuntimeApp, podVolumes []types.Volume, convertedFromDocker bool) ([]Mount, error) {
    78  	app := ra.App
    79  
    80  	var genMnts []Mount
    81  
    82  	vols := make(map[types.ACName]types.Volume)
    83  	for _, v := range podVolumes {
    84  		vols[v.Name] = v
    85  	}
    86  
    87  	// RuntimeApps have mounts, whereas Apps have mountPoints. mountPoints are partially for
    88  	// Docker compat; since apps can declare mountpoints. However, if we just run with rkt run,
    89  	// then we'll only have a Mount and no corresponding MountPoint.
    90  	// Furthermore, Mounts can have embedded volumes in the case of the CRI.
    91  	// So, we generate a pile of Mounts and their corresponding Volume
    92  
    93  	// Map of hostpath -> Mount
    94  	mnts := make(map[string]schema.Mount)
    95  
    96  	// Check runtimeApp's Mounts
    97  	for _, m := range ra.Mounts {
    98  		mnts[m.Path] = m
    99  
   100  		vol := m.AppVolume // Mounts can supply a volume
   101  		if vol == nil {
   102  			vv, ok := vols[m.Volume]
   103  			if !ok {
   104  				return nil, fmt.Errorf("could not find volume %s", m.Volume)
   105  			}
   106  			vol = &vv
   107  		}
   108  
   109  		// Find a corresponding MountPoint, which is optional
   110  		ro := false
   111  		for _, mp := range ra.App.MountPoints {
   112  			if mp.Name == m.Volume {
   113  				ro = mp.ReadOnly
   114  				break
   115  			}
   116  		}
   117  		if vol.ReadOnly != nil {
   118  			ro = *vol.ReadOnly
   119  		}
   120  
   121  		switch vol.Kind {
   122  		case "host":
   123  		case "empty":
   124  		default:
   125  			return nil, fmt.Errorf("Volume %s has invalid kind %s", vol.Name, vol.Kind)
   126  		}
   127  		genMnts = append(genMnts,
   128  			Mount{
   129  				Mount:          m,
   130  				DockerImplicit: false,
   131  				ReadOnly:       ro,
   132  				Volume:         *vol,
   133  			})
   134  	}
   135  
   136  	// Now, match up MountPoints with Mounts or Volumes
   137  	// If there's no Mount and no Volume, generate an empty volume
   138  	for _, mp := range app.MountPoints {
   139  		// there's already a Mount for this MountPoint, stop
   140  		if _, ok := mnts[mp.Path]; ok {
   141  			continue
   142  		}
   143  
   144  		// No Mount, try to match based on volume name
   145  		vol, ok := vols[mp.Name]
   146  		// there is no volume for this mount point, creating an "empty" volume
   147  		// implicitly
   148  		if !ok {
   149  			defaultMode := "0755"
   150  			defaultUID := 0
   151  			defaultGID := 0
   152  			uniqName := ra.Name + "-" + mp.Name
   153  			emptyVol := types.Volume{
   154  				Name: uniqName,
   155  				Kind: "empty",
   156  				Mode: &defaultMode,
   157  				UID:  &defaultUID,
   158  				GID:  &defaultGID,
   159  			}
   160  
   161  			log.Printf("warning: no volume specified for mount point %q, implicitly creating an \"empty\" volume. This volume will be removed when the pod is garbage-collected.", mp.Name)
   162  			if convertedFromDocker {
   163  				log.Printf("Docker converted image, initializing implicit volume with data contained at the mount point %q.", mp.Name)
   164  			}
   165  
   166  			vols[uniqName] = emptyVol
   167  			genMnts = append(genMnts,
   168  				Mount{
   169  					Mount: schema.Mount{
   170  						Volume: uniqName,
   171  						Path:   mp.Path,
   172  					},
   173  					Volume:         emptyVol,
   174  					ReadOnly:       mp.ReadOnly,
   175  					DockerImplicit: convertedFromDocker,
   176  				})
   177  		} else {
   178  			ro := mp.ReadOnly
   179  			if vol.ReadOnly != nil {
   180  				ro = *vol.ReadOnly
   181  			}
   182  			genMnts = append(genMnts,
   183  				Mount{
   184  					Mount: schema.Mount{
   185  						Volume: vol.Name,
   186  						Path:   mp.Path,
   187  					},
   188  					Volume:         vol,
   189  					ReadOnly:       ro,
   190  					DockerImplicit: false,
   191  				})
   192  		}
   193  	}
   194  
   195  	return genMnts, nil
   196  }
   197  
   198  // PrepareMountpoints creates and sets permissions for empty volumes.
   199  // If the mountpoint comes from a Docker image and it is an implicit empty
   200  // volume, we copy files from the image to the volume, see
   201  // https://docs.docker.com/engine/userguide/containers/dockervolumes/#data-volumes
   202  func PrepareMountpoints(volPath string, targetPath string, vol *types.Volume, dockerImplicit bool) error {
   203  	if vol.Kind != "empty" {
   204  		return nil
   205  	}
   206  
   207  	diag.Printf("creating an empty volume folder for sharing: %q", volPath)
   208  	m, err := strconv.ParseUint(*vol.Mode, 8, 32)
   209  	if err != nil {
   210  		return errwrap.Wrap(fmt.Errorf("invalid mode %q for volume %q", *vol.Mode, vol.Name), err)
   211  	}
   212  	mode := os.FileMode(m)
   213  	Uid := *vol.UID
   214  	Gid := *vol.GID
   215  
   216  	if dockerImplicit {
   217  		fi, err := os.Stat(targetPath)
   218  		if err == nil {
   219  			// the directory exists in the image, let's set the same
   220  			// permissions and copy files from there to the empty volume
   221  			mode = fi.Mode()
   222  			Uid = int(fi.Sys().(*syscall.Stat_t).Uid)
   223  			Gid = int(fi.Sys().(*syscall.Stat_t).Gid)
   224  
   225  			if err := fileutil.CopyTree(targetPath, volPath, user.NewBlankUidRange()); err != nil {
   226  				return errwrap.Wrap(fmt.Errorf("error copying image files to empty volume %q", volPath), err)
   227  			}
   228  		}
   229  	}
   230  
   231  	if err := os.MkdirAll(volPath, 0770); err != nil {
   232  		return errwrap.Wrap(fmt.Errorf("error creating %q", volPath), err)
   233  	}
   234  	if err := os.Chown(volPath, Uid, Gid); err != nil {
   235  		return errwrap.Wrap(fmt.Errorf("could not change owner of %q", volPath), err)
   236  	}
   237  	if err := os.Chmod(volPath, mode); err != nil {
   238  		return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", volPath), err)
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  // BindMount, well, bind mounts a source in to a destination. This will
   245  // do some bookkeeping:
   246  // * evaluate all symlinks
   247  // * ensure the source exists
   248  // * recursively create the destination
   249  func BindMount(mnt fs.MountUnmounter, source, destination string, readOnly bool) error {
   250  	absSource, err := filepath.EvalSymlinks(source)
   251  	if err != nil {
   252  		return errwrap.Wrap(fmt.Errorf("Could not resolve symlink for source %v", source), err)
   253  	}
   254  
   255  	if err := EnsureTargetExists(absSource, destination); err != nil {
   256  		return errwrap.Wrap(fmt.Errorf("Could not create destination mount point: %v", destination), err)
   257  	} else if err := mnt.Mount(absSource, destination, "bind", syscall.MS_BIND, ""); err != nil {
   258  		return errwrap.Wrap(fmt.Errorf("Could not bind mount %v to %v", absSource, destination), err)
   259  	}
   260  	if readOnly {
   261  		err := mnt.Mount(source, destination, "bind", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "")
   262  
   263  		// If we failed to remount ro, unmount
   264  		if err != nil {
   265  			mnt.Unmount(destination, 0) // if this fails, oh well
   266  			return errwrap.Wrap(fmt.Errorf("Could not remount %v read-only", destination), err)
   267  		}
   268  	}
   269  	return nil
   270  }
   271  
   272  // EnsureTargetExists will recursively create a given mountpoint. If directories
   273  // are created, their permissions are initialized to common.SharedVolumePerm
   274  func EnsureTargetExists(source, destination string) error {
   275  	fileInfo, err := os.Stat(source)
   276  	if err != nil {
   277  		return errwrap.Wrap(fmt.Errorf("could not stat source location: %v", source), err)
   278  	}
   279  
   280  	targetPathParent, _ := filepath.Split(destination)
   281  	if err := os.MkdirAll(targetPathParent, common.SharedVolumePerm); err != nil {
   282  		return errwrap.Wrap(fmt.Errorf("could not create parent directory: %v", targetPathParent), err)
   283  	}
   284  
   285  	if fileInfo.IsDir() {
   286  		if err := os.Mkdir(destination, common.SharedVolumePerm); err != nil && !os.IsExist(err) {
   287  			return errwrap.Wrap(errors.New("could not create destination directory "+destination), err)
   288  		}
   289  	} else {
   290  		if file, err := os.OpenFile(destination, os.O_CREATE, common.SharedVolumePerm); err != nil {
   291  			return errwrap.Wrap(errors.New("could not create destination file"), err)
   292  		} else {
   293  			file.Close()
   294  		}
   295  	}
   296  	return nil
   297  }