2014-12-03 02:43:38 +00:00
|
|
|
// +build linux
|
|
|
|
|
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors.
|
2014-12-03 02:43:38 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package mount
|
|
|
|
|
|
|
|
import (
|
2018-01-26 04:59:41 +00:00
|
|
|
"errors"
|
2014-12-03 02:43:38 +00:00
|
|
|
"fmt"
|
2018-03-05 08:14:44 +00:00
|
|
|
"io/ioutil"
|
2014-12-03 02:43:38 +00:00
|
|
|
"os"
|
2015-04-03 01:08:04 +00:00
|
|
|
"os/exec"
|
2017-09-13 08:05:55 +00:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2014-12-03 02:43:38 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2017-07-20 11:44:19 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2017-01-11 14:09:48 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2017-08-30 14:45:04 +00:00
|
|
|
utilio "k8s.io/kubernetes/pkg/util/io"
|
2017-07-19 05:58:53 +00:00
|
|
|
utilexec "k8s.io/utils/exec"
|
2014-12-03 02:43:38 +00:00
|
|
|
)
|
|
|
|
|
2015-04-03 01:08:04 +00:00
|
|
|
const (
|
|
|
|
// How many times to retry for a consistent read of /proc/mounts.
|
|
|
|
maxListTries = 3
|
2015-05-04 14:51:46 +00:00
|
|
|
// Number of fields per line in /proc/mounts as per the fstab man page.
|
2015-04-03 01:08:04 +00:00
|
|
|
expectedNumFieldsPerLine = 6
|
2015-05-04 14:51:46 +00:00
|
|
|
// Location of the mount file to use
|
|
|
|
procMountsPath = "/proc/mounts"
|
2017-08-30 14:45:04 +00:00
|
|
|
// Location of the mountinfo file
|
|
|
|
procMountInfoPath = "/proc/self/mountinfo"
|
2015-12-08 15:51:12 +00:00
|
|
|
// 'fsck' found errors and corrected them
|
|
|
|
fsckErrorsCorrected = 1
|
|
|
|
// 'fsck' found errors but exited without correcting them
|
|
|
|
fsckErrorsUncorrected = 4
|
2018-03-05 08:14:44 +00:00
|
|
|
|
|
|
|
// place for subpath mounts
|
|
|
|
containerSubPathDirectoryName = "volume-subpaths"
|
|
|
|
// syscall.Openat flags used to traverse directories not following symlinks
|
2018-03-22 19:04:39 +00:00
|
|
|
nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
|
|
|
|
// flags for getting file descriptor without following the symlink
|
|
|
|
openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
|
2015-12-08 15:51:12 +00:00
|
|
|
)
|
|
|
|
|
2015-05-04 14:51:46 +00:00
|
|
|
// Mounter provides the default implementation of mount.Interface
|
|
|
|
// for the linux platform. This implementation assumes that the
|
|
|
|
// kubelet is running in the host's root mount namespace.
|
2016-10-17 23:18:29 +00:00
|
|
|
type Mounter struct {
|
2016-11-04 23:13:07 +00:00
|
|
|
mounterPath string
|
2017-08-08 13:40:27 +00:00
|
|
|
withSystemd bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a mount.Interface for the current system.
|
|
|
|
// It provides options to override the default mounter behavior.
|
|
|
|
// mounterPath allows using an alternative to `/bin/mount` for mounting.
|
|
|
|
func New(mounterPath string) Interface {
|
|
|
|
return &Mounter{
|
|
|
|
mounterPath: mounterPath,
|
|
|
|
withSystemd: detectSystemd(),
|
|
|
|
}
|
2016-10-17 23:18:29 +00:00
|
|
|
}
|
2014-12-03 02:43:38 +00:00
|
|
|
|
2015-04-03 01:08:04 +00:00
|
|
|
// Mount mounts source to target as fstype with given options. 'source' and 'fstype' must
|
2018-02-09 06:53:53 +00:00
|
|
|
// be an empty string in case it's not required, e.g. for remount, or for auto filesystem
|
2017-09-04 01:34:10 +00:00
|
|
|
// type, where kernel handles fstype for you. The mount 'options' is a list of options,
|
2015-04-03 01:08:04 +00:00
|
|
|
// currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is
|
|
|
|
// required, call Mount with an empty string list or nil.
|
|
|
|
func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
|
2017-02-23 00:35:59 +00:00
|
|
|
// Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty.
|
2017-09-04 01:34:10 +00:00
|
|
|
// All Linux distros are expected to be shipped with a mount utility that a support bind mounts.
|
2017-02-23 00:35:59 +00:00
|
|
|
mounterPath := ""
|
2015-05-04 14:51:46 +00:00
|
|
|
bind, bindRemountOpts := isBind(options)
|
|
|
|
if bind {
|
2017-08-08 13:40:27 +00:00
|
|
|
err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, []string{"bind"})
|
2015-05-04 14:51:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-08-08 13:40:27 +00:00
|
|
|
return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts)
|
2015-05-04 14:51:46 +00:00
|
|
|
}
|
2016-12-02 22:36:46 +00:00
|
|
|
// The list of filesystems that require containerized mounter on GCI image cluster
|
2017-02-23 00:35:59 +00:00
|
|
|
fsTypesNeedMounter := sets.NewString("nfs", "glusterfs", "ceph", "cifs")
|
2016-12-02 22:36:46 +00:00
|
|
|
if fsTypesNeedMounter.Has(fstype) {
|
2016-11-04 23:13:07 +00:00
|
|
|
mounterPath = mounter.mounterPath
|
|
|
|
}
|
2017-08-08 13:40:27 +00:00
|
|
|
return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options)
|
2015-05-04 14:51:46 +00:00
|
|
|
}
|
|
|
|
|
2017-02-23 00:35:59 +00:00
|
|
|
// doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used.
|
2017-08-08 13:40:27 +00:00
|
|
|
func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error {
|
2015-05-04 14:51:46 +00:00
|
|
|
mountArgs := makeMountArgs(source, target, fstype, options)
|
2017-02-23 00:35:59 +00:00
|
|
|
if len(mounterPath) > 0 {
|
|
|
|
mountArgs = append([]string{mountCmd}, mountArgs...)
|
|
|
|
mountCmd = mounterPath
|
|
|
|
}
|
2016-10-17 23:18:29 +00:00
|
|
|
|
2017-08-08 13:40:27 +00:00
|
|
|
if m.withSystemd {
|
2017-07-26 14:14:39 +00:00
|
|
|
// Try to run mount via systemd-run --scope. This will escape the
|
|
|
|
// service where kubelet runs and any fuse daemons will be started in a
|
|
|
|
// specific scope. kubelet service than can be restarted without killing
|
|
|
|
// these fuse daemons.
|
|
|
|
//
|
|
|
|
// Complete command line (when mounterPath is not used):
|
|
|
|
// systemd-run --description=... --scope -- mount -t <type> <what> <where>
|
|
|
|
//
|
|
|
|
// Expected flow:
|
|
|
|
// * systemd-run creates a transient scope (=~ cgroup) and executes its
|
|
|
|
// argument (/bin/mount) there.
|
|
|
|
// * mount does its job, forks a fuse daemon if necessary and finishes.
|
|
|
|
// (systemd-run --scope finishes at this point, returning mount's exit
|
|
|
|
// code and stdout/stderr - thats one of --scope benefits).
|
|
|
|
// * systemd keeps the fuse daemon running in the scope (i.e. in its own
|
|
|
|
// cgroup) until the fuse daemon dies (another --scope benefit).
|
|
|
|
// Kubelet service can be restarted and the fuse daemon survives.
|
|
|
|
// * When the fuse daemon dies (e.g. during unmount) systemd removes the
|
|
|
|
// scope automatically.
|
|
|
|
//
|
|
|
|
// systemd-mount is not used because it's too new for older distros
|
|
|
|
// (CentOS 7, Debian Jessie).
|
2017-08-08 13:40:27 +00:00
|
|
|
mountCmd, mountArgs = addSystemdScope("systemd-run", target, mountCmd, mountArgs)
|
2017-07-26 14:14:39 +00:00
|
|
|
} else {
|
|
|
|
// No systemd-run on the host (or we failed to check it), assume kubelet
|
|
|
|
// does not run as a systemd service.
|
|
|
|
// No code here, mountCmd and mountArgs are already populated.
|
|
|
|
}
|
|
|
|
|
2016-09-27 23:12:57 +00:00
|
|
|
glog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs)
|
2016-10-17 23:18:29 +00:00
|
|
|
command := exec.Command(mountCmd, mountArgs...)
|
2015-05-04 14:51:46 +00:00
|
|
|
output, err := command.CombinedOutput()
|
|
|
|
if err != nil {
|
2017-07-26 14:14:39 +00:00
|
|
|
args := strings.Join(mountArgs, " ")
|
|
|
|
glog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n", err, mountCmd, args, string(output))
|
|
|
|
return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n",
|
|
|
|
err, mountCmd, args, string(output))
|
2015-05-04 14:51:46 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-09-13 08:05:55 +00:00
|
|
|
// GetMountRefs finds all other references to the device referenced
|
|
|
|
// by mountPath; returns a list of paths.
|
|
|
|
func GetMountRefs(mounter Interface, mountPath string) ([]string, error) {
|
|
|
|
mps, err := mounter.List()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Find the device name.
|
|
|
|
deviceName := ""
|
|
|
|
// If mountPath is symlink, need get its target path.
|
|
|
|
slTarget, err := filepath.EvalSymlinks(mountPath)
|
|
|
|
if err != nil {
|
|
|
|
slTarget = mountPath
|
|
|
|
}
|
|
|
|
for i := range mps {
|
|
|
|
if mps[i].Path == slTarget {
|
|
|
|
deviceName = mps[i].Device
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find all references to the device.
|
|
|
|
var refs []string
|
|
|
|
if deviceName == "" {
|
|
|
|
glog.Warningf("could not determine device for path: %q", mountPath)
|
|
|
|
} else {
|
|
|
|
for i := range mps {
|
|
|
|
if mps[i].Device == deviceName && mps[i].Path != slTarget {
|
|
|
|
refs = append(refs, mps[i].Path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return refs, nil
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:40:27 +00:00
|
|
|
// detectSystemd returns true if OS runs with systemd as init. When not sure
|
|
|
|
// (permission errors, ...), it returns false.
|
|
|
|
// There may be different ways how to detect systemd, this one makes sure that
|
|
|
|
// systemd-runs (needed by Mount()) works.
|
|
|
|
func detectSystemd() bool {
|
|
|
|
if _, err := exec.LookPath("systemd-run"); err != nil {
|
|
|
|
glog.V(2).Infof("Detected OS without systemd")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// Try to run systemd-run --scope /bin/true, that should be enough
|
|
|
|
// to make sure that systemd is really running and not just installed,
|
|
|
|
// which happens when running in a container with a systemd-based image
|
|
|
|
// but with different pid 1.
|
|
|
|
cmd := exec.Command("systemd-run", "--description=Kubernetes systemd probe", "--scope", "true")
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
glog.V(2).Infof("Cannot run systemd-run, assuming non-systemd OS")
|
|
|
|
glog.V(4).Infof("systemd-run failed with: %v", err)
|
|
|
|
glog.V(4).Infof("systemd-run output: %s", string(output))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
glog.V(2).Infof("Detected OS with systemd")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-05-04 14:51:46 +00:00
|
|
|
// makeMountArgs makes the arguments to the mount(8) command.
|
|
|
|
func makeMountArgs(source, target, fstype string, options []string) []string {
|
2015-04-03 01:08:04 +00:00
|
|
|
// Build mount command as follows:
|
|
|
|
// mount [-t $fstype] [-o $options] [$source] $target
|
|
|
|
mountArgs := []string{}
|
|
|
|
if len(fstype) > 0 {
|
|
|
|
mountArgs = append(mountArgs, "-t", fstype)
|
|
|
|
}
|
|
|
|
if len(options) > 0 {
|
|
|
|
mountArgs = append(mountArgs, "-o", strings.Join(options, ","))
|
|
|
|
}
|
|
|
|
if len(source) > 0 {
|
|
|
|
mountArgs = append(mountArgs, source)
|
|
|
|
}
|
|
|
|
mountArgs = append(mountArgs, target)
|
2015-05-04 14:51:46 +00:00
|
|
|
|
|
|
|
return mountArgs
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 14:14:39 +00:00
|
|
|
// addSystemdScope adds "system-run --scope" to given command line
|
|
|
|
func addSystemdScope(systemdRunPath, mountName, command string, args []string) (string, []string) {
|
|
|
|
descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
|
|
|
|
systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
|
|
|
|
return systemdRunPath, append(systemdRunArgs, args...)
|
|
|
|
}
|
|
|
|
|
2015-05-04 14:51:46 +00:00
|
|
|
// Unmount unmounts the target.
|
2015-04-03 01:08:04 +00:00
|
|
|
func (mounter *Mounter) Unmount(target string) error {
|
2016-09-27 23:12:57 +00:00
|
|
|
glog.V(4).Infof("Unmounting %s", target)
|
2015-04-03 01:08:04 +00:00
|
|
|
command := exec.Command("umount", target)
|
|
|
|
output, err := command.CombinedOutput()
|
|
|
|
if err != nil {
|
2015-10-21 15:52:02 +00:00
|
|
|
return fmt.Errorf("Unmount failed: %v\nUnmounting arguments: %s\nOutput: %s\n", err, target, string(output))
|
2015-04-03 01:08:04 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2014-12-03 02:43:38 +00:00
|
|
|
|
2015-04-01 02:08:33 +00:00
|
|
|
// List returns a list of all mounted filesystems.
|
2015-05-04 14:51:46 +00:00
|
|
|
func (*Mounter) List() ([]MountPoint, error) {
|
|
|
|
return listProcMounts(procMountsPath)
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
|
|
|
|
2017-07-01 20:51:28 +00:00
|
|
|
func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool {
|
|
|
|
deletedDir := fmt.Sprintf("%s\\040(deleted)", dir)
|
|
|
|
return ((mp.Path == dir) || (mp.Path == deletedDir))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) {
|
|
|
|
return IsNotMountPoint(mounter, dir)
|
|
|
|
}
|
|
|
|
|
2015-04-16 23:49:53 +00:00
|
|
|
// IsLikelyNotMountPoint determines if a directory is not a mountpoint.
|
|
|
|
// It is fast but not necessarily ALWAYS correct. If the path is in fact
|
|
|
|
// a bind mount from one part of a mount to another it will not be detected.
|
|
|
|
// mkdir /tmp/a /tmp/b; mount --bin /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b")
|
|
|
|
// will return true. When in fact /tmp/b is a mount point. If this situation
|
|
|
|
// if of interest to you, don't use this function...
|
|
|
|
func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
|
2015-04-01 02:08:33 +00:00
|
|
|
stat, err := os.Stat(file)
|
|
|
|
if err != nil {
|
2015-04-16 23:49:53 +00:00
|
|
|
return true, err
|
2015-04-01 02:08:33 +00:00
|
|
|
}
|
2018-01-18 05:52:47 +00:00
|
|
|
rootStat, err := os.Lstat(filepath.Dir(strings.TrimSuffix(file, "/")))
|
2015-04-01 02:08:33 +00:00
|
|
|
if err != nil {
|
2015-04-16 23:49:53 +00:00
|
|
|
return true, err
|
2015-04-01 02:08:33 +00:00
|
|
|
}
|
2015-04-16 23:49:53 +00:00
|
|
|
// If the directory has a different device as parent, then it is a mountpoint.
|
|
|
|
if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
2015-04-01 02:08:33 +00:00
|
|
|
}
|
|
|
|
|
2016-07-06 17:42:56 +00:00
|
|
|
// DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
|
2016-09-27 23:12:57 +00:00
|
|
|
// If pathname is not a device, log and return false with nil error.
|
|
|
|
// If open returns errno EBUSY, return true with nil error.
|
|
|
|
// If open returns nil, return false with nil error.
|
|
|
|
// Otherwise, return false with error
|
2016-07-06 17:42:56 +00:00
|
|
|
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
|
|
|
|
return exclusiveOpenFailsOnDevice(pathname)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PathIsDevice uses FileInfo returned from os.Stat to check if path refers
|
|
|
|
// to a device.
|
|
|
|
func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
|
2017-09-08 10:15:03 +00:00
|
|
|
pathType, err := mounter.GetFileType(pathname)
|
|
|
|
isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
|
|
|
|
return isDevice, err
|
2016-07-06 17:42:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func exclusiveOpenFailsOnDevice(pathname string) (bool, error) {
|
2017-09-08 10:15:03 +00:00
|
|
|
var isDevice bool
|
|
|
|
finfo, err := os.Stat(pathname)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
isDevice = false
|
|
|
|
}
|
|
|
|
// err in call to os.Stat
|
2016-09-27 23:12:57 +00:00
|
|
|
if err != nil {
|
2016-07-06 17:42:56 +00:00
|
|
|
return false, fmt.Errorf(
|
|
|
|
"PathIsDevice failed for path %q: %v",
|
|
|
|
pathname,
|
|
|
|
err)
|
|
|
|
}
|
2017-09-08 10:15:03 +00:00
|
|
|
// path refers to a device
|
|
|
|
if finfo.Mode()&os.ModeDevice != 0 {
|
|
|
|
isDevice = true
|
|
|
|
}
|
|
|
|
|
2016-09-27 23:12:57 +00:00
|
|
|
if !isDevice {
|
2018-02-09 06:53:53 +00:00
|
|
|
glog.Errorf("Path %q is not referring to a device.", pathname)
|
2016-09-27 23:12:57 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
2017-07-20 11:44:19 +00:00
|
|
|
fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL, 0)
|
2016-07-06 17:42:56 +00:00
|
|
|
// If the device is in use, open will return an invalid fd.
|
|
|
|
// When this happens, it is expected that Close will fail and throw an error.
|
2017-07-20 11:44:19 +00:00
|
|
|
defer unix.Close(fd)
|
2016-07-06 17:42:56 +00:00
|
|
|
if errno == nil {
|
|
|
|
// device not in use
|
|
|
|
return false, nil
|
2017-07-20 11:44:19 +00:00
|
|
|
} else if errno == unix.EBUSY {
|
2016-07-06 17:42:56 +00:00
|
|
|
// device is in use
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
// error during call to Open
|
|
|
|
return false, errno
|
|
|
|
}
|
|
|
|
|
2016-06-23 19:46:21 +00:00
|
|
|
//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point
|
|
|
|
func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
|
|
|
|
return getDeviceNameFromMount(mounter, mountPath, pluginDir)
|
|
|
|
}
|
|
|
|
|
2017-09-13 08:05:55 +00:00
|
|
|
// getDeviceNameFromMount find the device name from /proc/mounts in which
|
|
|
|
// the mount path reference should match the given plugin directory. In case no mount path reference
|
|
|
|
// matches, returns the volume name taken from its given mountPath
|
|
|
|
func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) {
|
|
|
|
refs, err := GetMountRefs(mounter, mountPath)
|
|
|
|
if err != nil {
|
|
|
|
glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(refs) == 0 {
|
|
|
|
glog.V(4).Infof("Directory %s is not mounted", mountPath)
|
|
|
|
return "", fmt.Errorf("directory %s is not mounted", mountPath)
|
|
|
|
}
|
|
|
|
basemountPath := path.Join(pluginDir, MountsInGlobalPDPath)
|
|
|
|
for _, ref := range refs {
|
|
|
|
if strings.HasPrefix(ref, basemountPath) {
|
|
|
|
volumeID, err := filepath.Rel(basemountPath, ref)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return volumeID, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return path.Base(mountPath), nil
|
|
|
|
}
|
|
|
|
|
2015-05-04 14:51:46 +00:00
|
|
|
func listProcMounts(mountFilePath string) ([]MountPoint, error) {
|
2017-08-30 14:45:04 +00:00
|
|
|
content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
|
2015-05-04 14:51:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-08-30 14:45:04 +00:00
|
|
|
return parseProcMounts(content)
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
|
|
|
|
2017-08-30 14:45:04 +00:00
|
|
|
func parseProcMounts(content []byte) ([]MountPoint, error) {
|
|
|
|
out := []MountPoint{}
|
|
|
|
lines := strings.Split(string(content), "\n")
|
|
|
|
for _, line := range lines {
|
|
|
|
if line == "" {
|
|
|
|
// the last split() item is empty string following the last \n
|
|
|
|
continue
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
|
|
|
fields := strings.Fields(line)
|
|
|
|
if len(fields) != expectedNumFieldsPerLine {
|
2017-08-30 14:45:04 +00:00
|
|
|
return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line)
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
|
|
|
|
2017-08-30 14:45:04 +00:00
|
|
|
mp := MountPoint{
|
|
|
|
Device: fields[0],
|
|
|
|
Path: fields[1],
|
|
|
|
Type: fields[2],
|
|
|
|
Opts: strings.Split(fields[3], ","),
|
|
|
|
}
|
2014-12-03 02:43:38 +00:00
|
|
|
|
2017-08-30 14:45:04 +00:00
|
|
|
freq, err := strconv.Atoi(fields[4])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mp.Freq = freq
|
2014-12-03 02:43:38 +00:00
|
|
|
|
2017-08-30 14:45:04 +00:00
|
|
|
pass, err := strconv.Atoi(fields[5])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
2017-08-30 14:45:04 +00:00
|
|
|
mp.Pass = pass
|
|
|
|
|
|
|
|
out = append(out, mp)
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
2017-08-30 14:45:04 +00:00
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) MakeRShared(path string) error {
|
2017-09-01 01:53:16 +00:00
|
|
|
return doMakeRShared(path, procMountInfoPath)
|
2014-12-03 02:43:38 +00:00
|
|
|
}
|
2015-11-06 20:37:46 +00:00
|
|
|
|
2017-09-08 10:15:03 +00:00
|
|
|
func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
|
2018-04-05 08:09:26 +00:00
|
|
|
return getFileType(pathname)
|
2017-09-08 10:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) MakeDir(pathname string) error {
|
|
|
|
err := os.MkdirAll(pathname, os.FileMode(0755))
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) MakeFile(pathname string) error {
|
|
|
|
f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
|
|
|
|
defer f.Close()
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) ExistsPath(pathname string) bool {
|
|
|
|
_, err := os.Stat(pathname)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-11-06 20:37:46 +00:00
|
|
|
// formatAndMount uses unix utils to format and mount the given disk
|
|
|
|
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
|
2018-01-26 04:59:41 +00:00
|
|
|
readOnly := false
|
|
|
|
for _, option := range options {
|
|
|
|
if option == "ro" {
|
|
|
|
readOnly = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-06 20:37:46 +00:00
|
|
|
options = append(options, "defaults")
|
|
|
|
|
2018-01-26 04:59:41 +00:00
|
|
|
if !readOnly {
|
|
|
|
// Run fsck on the disk to fix repairable issues, only do this for volumes requested as rw.
|
|
|
|
glog.V(4).Infof("Checking for issues with fsck on disk: %s", source)
|
|
|
|
args := []string{"-a", source}
|
|
|
|
out, err := mounter.Exec.Run("fsck", args...)
|
|
|
|
if err != nil {
|
|
|
|
ee, isExitError := err.(utilexec.ExitError)
|
|
|
|
switch {
|
|
|
|
case err == utilexec.ErrExecutableNotFound:
|
|
|
|
glog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.")
|
|
|
|
case isExitError && ee.ExitStatus() == fsckErrorsCorrected:
|
|
|
|
glog.Infof("Device %s has errors which were corrected by fsck.", source)
|
|
|
|
case isExitError && ee.ExitStatus() == fsckErrorsUncorrected:
|
|
|
|
return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s.", source, string(out))
|
|
|
|
case isExitError && ee.ExitStatus() > fsckErrorsUncorrected:
|
|
|
|
glog.Infof("`fsck` error %s", string(out))
|
|
|
|
}
|
2015-09-18 21:25:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-06 20:37:46 +00:00
|
|
|
// Try to mount the disk
|
2016-04-22 15:59:36 +00:00
|
|
|
glog.V(4).Infof("Attempting to mount disk: %s %s %s", fstype, source, target)
|
2016-08-26 10:49:26 +00:00
|
|
|
mountErr := mounter.Interface.Mount(source, target, fstype, options)
|
|
|
|
if mountErr != nil {
|
|
|
|
// Mount failed. This indicates either that the disk is unformatted or
|
|
|
|
// it contains an unexpected filesystem.
|
2017-11-20 17:59:48 +00:00
|
|
|
existingFormat, err := mounter.GetDiskFormat(source)
|
2016-08-26 10:49:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if existingFormat == "" {
|
2018-01-26 04:59:41 +00:00
|
|
|
if readOnly {
|
|
|
|
// Don't attempt to format if mounting as readonly, return an error to reflect this.
|
|
|
|
return errors.New("failed to mount unformatted volume as read only")
|
|
|
|
}
|
|
|
|
|
2015-11-06 20:37:46 +00:00
|
|
|
// Disk is unformatted so format it.
|
2018-01-26 04:59:41 +00:00
|
|
|
args := []string{source}
|
2015-11-06 20:37:46 +00:00
|
|
|
// Use 'ext4' as the default
|
|
|
|
if len(fstype) == 0 {
|
|
|
|
fstype = "ext4"
|
|
|
|
}
|
2016-08-26 10:49:26 +00:00
|
|
|
|
2015-11-06 20:37:46 +00:00
|
|
|
if fstype == "ext4" || fstype == "ext3" {
|
2016-12-18 10:08:51 +00:00
|
|
|
args = []string{"-F", source}
|
2015-11-06 20:37:46 +00:00
|
|
|
}
|
2016-04-22 15:59:36 +00:00
|
|
|
glog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args)
|
2017-08-14 10:16:27 +00:00
|
|
|
_, err := mounter.Exec.Run("mkfs."+fstype, args...)
|
2015-11-06 20:37:46 +00:00
|
|
|
if err == nil {
|
2016-02-12 19:33:32 +00:00
|
|
|
// the disk has been formatted successfully try to mount it again.
|
2016-04-22 15:59:36 +00:00
|
|
|
glog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target)
|
2015-11-06 20:37:46 +00:00
|
|
|
return mounter.Interface.Mount(source, target, fstype, options)
|
|
|
|
}
|
2016-04-22 15:59:36 +00:00
|
|
|
glog.Errorf("format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", source, fstype, target, options, err)
|
2015-11-06 20:37:46 +00:00
|
|
|
return err
|
2016-08-26 10:49:26 +00:00
|
|
|
} else {
|
|
|
|
// Disk is already formatted and failed to mount
|
|
|
|
if len(fstype) == 0 || fstype == existingFormat {
|
|
|
|
// This is mount error
|
|
|
|
return mountErr
|
|
|
|
} else {
|
|
|
|
// Block device is formatted with unexpected filesystem, let the user know
|
2017-03-20 12:08:13 +00:00
|
|
|
return fmt.Errorf("failed to mount the volume as %q, it already contains %s. Mount error: %v", fstype, existingFormat, mountErr)
|
2016-08-26 10:49:26 +00:00
|
|
|
}
|
2015-11-06 20:37:46 +00:00
|
|
|
}
|
|
|
|
}
|
2016-08-26 10:49:26 +00:00
|
|
|
return mountErr
|
2015-11-06 20:37:46 +00:00
|
|
|
}
|
|
|
|
|
2018-01-30 15:58:44 +00:00
|
|
|
// GetDiskFormat uses 'blkid' to see if the given disk is unformated
|
2017-11-20 17:59:48 +00:00
|
|
|
func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) {
|
2018-02-02 10:47:24 +00:00
|
|
|
args := []string{"-p", "-s", "TYPE", "-s", "PTTYPE", "-o", "export", disk}
|
2018-01-30 15:58:44 +00:00
|
|
|
glog.V(4).Infof("Attempting to determine if disk %q is formatted using blkid with args: (%v)", disk, args)
|
|
|
|
dataOut, err := mounter.Exec.Run("blkid", args...)
|
2017-03-20 12:08:13 +00:00
|
|
|
output := string(dataOut)
|
2018-02-05 03:21:35 +00:00
|
|
|
glog.V(4).Infof("Output: %q, err: %v", output, err)
|
2015-11-06 20:37:46 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2018-02-05 03:21:35 +00:00
|
|
|
if exit, ok := err.(utilexec.ExitError); ok {
|
|
|
|
if exit.ExitStatus() == 2 {
|
|
|
|
// Disk device is unformatted.
|
|
|
|
// For `blkid`, if the specified token (TYPE/PTTYPE, etc) was
|
|
|
|
// not found, or no (specified) devices could be identified, an
|
|
|
|
// exit code of 2 is returned.
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
}
|
2016-04-22 15:59:36 +00:00
|
|
|
glog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err)
|
2016-08-26 10:49:26 +00:00
|
|
|
return "", err
|
2015-11-06 20:37:46 +00:00
|
|
|
}
|
|
|
|
|
2018-02-02 10:47:24 +00:00
|
|
|
var fstype, pttype string
|
|
|
|
|
|
|
|
lines := strings.Split(output, "\n")
|
|
|
|
for _, l := range lines {
|
|
|
|
if len(l) <= 0 {
|
|
|
|
// Ignore empty line.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cs := strings.Split(l, "=")
|
|
|
|
if len(cs) != 2 {
|
|
|
|
return "", fmt.Errorf("blkid returns invalid output: %s", output)
|
|
|
|
}
|
|
|
|
// TYPE is filesystem type, and PTTYPE is partition table type, according
|
|
|
|
// to https://www.kernel.org/pub/linux/utils/util-linux/v2.21/libblkid-docs/.
|
|
|
|
if cs[0] == "TYPE" {
|
|
|
|
fstype = cs[1]
|
|
|
|
} else if cs[0] == "PTTYPE" {
|
|
|
|
pttype = cs[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(pttype) > 0 {
|
|
|
|
glog.V(4).Infof("Disk %s detected partition table type: %s", pttype)
|
|
|
|
// Returns a special non-empty string as filesystem type, then kubelet
|
|
|
|
// will not format it.
|
|
|
|
return "unknown data, probably partitions", nil
|
2017-03-20 12:08:13 +00:00
|
|
|
}
|
|
|
|
|
2018-02-02 10:47:24 +00:00
|
|
|
return fstype, nil
|
2015-11-06 20:37:46 +00:00
|
|
|
}
|
2017-08-30 14:45:04 +00:00
|
|
|
|
|
|
|
// isShared returns true, if given path is on a mount point that has shared
|
|
|
|
// mount propagation.
|
|
|
|
func isShared(path string, filename string) (bool, error) {
|
|
|
|
infos, err := parseMountInfo(filename)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// process /proc/xxx/mountinfo in backward order and find the first mount
|
|
|
|
// point that is prefix of 'path' - that's the mount where path resides
|
|
|
|
var info *mountInfo
|
|
|
|
for i := len(infos) - 1; i >= 0; i-- {
|
|
|
|
if strings.HasPrefix(path, infos[i].mountPoint) {
|
|
|
|
info = &infos[i]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if info == nil {
|
|
|
|
return false, fmt.Errorf("cannot find mount point for %q", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse optional parameters
|
|
|
|
for _, opt := range info.optional {
|
|
|
|
if strings.HasPrefix(opt, "shared:") {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type mountInfo struct {
|
2018-03-05 08:14:44 +00:00
|
|
|
// Path of the mount point
|
2017-08-30 14:45:04 +00:00
|
|
|
mountPoint string
|
|
|
|
// list of "optional parameters", mount propagation is one of them
|
|
|
|
optional []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseMountInfo parses /proc/xxx/mountinfo.
|
|
|
|
func parseMountInfo(filename string) ([]mountInfo, error) {
|
|
|
|
content, err := utilio.ConsistentRead(filename, maxListTries)
|
|
|
|
if err != nil {
|
|
|
|
return []mountInfo{}, err
|
|
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
infos := []mountInfo{}
|
|
|
|
|
|
|
|
for _, line := range strings.Split(contentStr, "\n") {
|
|
|
|
if line == "" {
|
|
|
|
// the last split() item is empty string following the last \n
|
|
|
|
continue
|
|
|
|
}
|
2018-03-05 08:14:44 +00:00
|
|
|
// See `man proc` for authoritative description of format of the file.
|
2017-08-30 14:45:04 +00:00
|
|
|
fields := strings.Fields(line)
|
|
|
|
if len(fields) < 7 {
|
|
|
|
return nil, fmt.Errorf("wrong number of fields in (expected %d, got %d): %s", 8, len(fields), line)
|
|
|
|
}
|
|
|
|
info := mountInfo{
|
|
|
|
mountPoint: fields[4],
|
|
|
|
optional: []string{},
|
|
|
|
}
|
2018-03-05 08:14:44 +00:00
|
|
|
// All fields until "-" are "optional fields".
|
2017-08-30 14:45:04 +00:00
|
|
|
for i := 6; i < len(fields) && fields[i] != "-"; i++ {
|
|
|
|
info.optional = append(info.optional, fields[i])
|
|
|
|
}
|
|
|
|
infos = append(infos, info)
|
|
|
|
}
|
|
|
|
return infos, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// doMakeRShared is common implementation of MakeRShared on Linux. It checks if
|
|
|
|
// path is shared and bind-mounts it as rshared if needed. mountCmd and
|
|
|
|
// mountArgs are expected to contain mount-like command, doMakeRShared will add
|
|
|
|
// '--bind <path> <path>' and '--make-rshared <path>' to mountArgs.
|
2017-09-01 01:53:16 +00:00
|
|
|
func doMakeRShared(path string, mountInfoFilename string) error {
|
2017-08-30 14:45:04 +00:00
|
|
|
shared, err := isShared(path, mountInfoFilename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if shared {
|
|
|
|
glog.V(4).Infof("Directory %s is already on a shared mount", path)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(2).Infof("Bind-mounting %q with shared mount propagation", path)
|
|
|
|
// mount --bind /var/lib/kubelet /var/lib/kubelet
|
|
|
|
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_BIND, "" /*data*/); err != nil {
|
|
|
|
return fmt.Errorf("failed to bind-mount %s: %v", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// mount --make-rshared /var/lib/kubelet
|
|
|
|
if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_SHARED|syscall.MS_REC, "" /*data*/); err != nil {
|
|
|
|
return fmt.Errorf("failed to make %s rshared: %v", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2018-03-05 08:14:44 +00:00
|
|
|
|
|
|
|
func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
|
|
|
|
newHostPath, err = doBindSubPath(mounter, subPath, os.Getpid())
|
|
|
|
// There is no action when the container starts. Bind-mount will be cleaned
|
|
|
|
// when container stops by CleanSubPaths.
|
|
|
|
cleanupAction = nil
|
|
|
|
return newHostPath, cleanupAction, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// This implementation is shared between Linux and NsEnterMounter
|
|
|
|
// kubeletPid is PID of kubelet in the PID namespace where bind-mount is done,
|
|
|
|
// i.e. pid on the *host* if kubelet runs in a container.
|
|
|
|
func doBindSubPath(mounter Interface, subpath Subpath, kubeletPid int) (hostPath string, err error) {
|
|
|
|
// Check early for symlink. This is just a pre-check to avoid bind-mount
|
|
|
|
// before the final check.
|
|
|
|
evalSubPath, err := filepath.EvalSymlinks(subpath.Path)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("evalSymlinks %q failed: %v", subpath.Path, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("doBindSubPath %q, full subpath %q for volumepath %q", subpath.Path, evalSubPath, subpath.VolumePath)
|
|
|
|
|
|
|
|
evalSubPath = filepath.Clean(evalSubPath)
|
|
|
|
if !pathWithinBase(evalSubPath, subpath.VolumePath) {
|
|
|
|
return "", fmt.Errorf("subpath %q not within volume path %q", evalSubPath, subpath.VolumePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare directory for bind mounts
|
|
|
|
// containerName is DNS label, i.e. safe as a directory name.
|
|
|
|
bindDir := filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName)
|
|
|
|
err = os.MkdirAll(bindDir, 0750)
|
|
|
|
if err != nil && !os.IsExist(err) {
|
|
|
|
return "", fmt.Errorf("error creating directory %s: %s", bindDir, err)
|
|
|
|
}
|
|
|
|
bindPathTarget := filepath.Join(bindDir, strconv.Itoa(subpath.VolumeMountIndex))
|
|
|
|
|
|
|
|
success := false
|
|
|
|
defer func() {
|
|
|
|
// Cleanup subpath on error
|
|
|
|
if !success {
|
|
|
|
glog.V(4).Infof("doBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
|
|
|
|
if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
|
|
|
|
glog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Check it's not already bind-mounted
|
|
|
|
notMount, err := IsNotMountPoint(mounter, bindPathTarget)
|
|
|
|
if err != nil {
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
return "", fmt.Errorf("error checking path %s for mount: %s", bindPathTarget, err)
|
|
|
|
}
|
|
|
|
// Ignore ErrorNotExist: the file/directory will be created below if it does not exist yet.
|
|
|
|
notMount = true
|
|
|
|
}
|
|
|
|
if !notMount {
|
|
|
|
// It's already mounted
|
|
|
|
glog.V(5).Infof("Skipping bind-mounting subpath %s: already mounted", bindPathTarget)
|
|
|
|
success = true
|
|
|
|
return bindPathTarget, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create target of the bind mount. A directory for directories, empty file
|
|
|
|
// for everything else.
|
|
|
|
t, err := os.Lstat(subpath.Path)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("lstat %s failed: %s", subpath.Path, err)
|
|
|
|
}
|
|
|
|
if t.Mode()&os.ModeDir > 0 {
|
|
|
|
if err = os.Mkdir(bindPathTarget, 0750); err != nil && !os.IsExist(err) {
|
|
|
|
return "", fmt.Errorf("error creating directory %s: %s", bindPathTarget, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// "/bin/touch <bindDir>".
|
|
|
|
// A file is enough for all possible targets (symlink, device, pipe,
|
|
|
|
// socket, ...), bind-mounting them into a file correctly changes type
|
|
|
|
// of the target file.
|
|
|
|
if err = ioutil.WriteFile(bindPathTarget, []byte{}, 0640); err != nil {
|
|
|
|
return "", fmt.Errorf("error creating file %s: %s", bindPathTarget, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Safe open subpath and get the fd
|
|
|
|
fd, err := doSafeOpen(evalSubPath, subpath.VolumePath)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error opening subpath %v: %v", evalSubPath, err)
|
|
|
|
}
|
|
|
|
defer syscall.Close(fd)
|
|
|
|
|
|
|
|
mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd)
|
|
|
|
|
|
|
|
// Do the bind mount
|
2018-04-24 00:13:27 +00:00
|
|
|
options := []string{"bind"}
|
|
|
|
if subpath.ReadOnly {
|
|
|
|
options = append(options, "ro")
|
|
|
|
}
|
|
|
|
|
2018-03-05 08:14:44 +00:00
|
|
|
glog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
|
2018-04-24 00:13:27 +00:00
|
|
|
if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
|
2018-03-05 08:14:44 +00:00
|
|
|
return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
success = true
|
|
|
|
glog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
|
|
|
|
return bindPathTarget, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) CleanSubPaths(podDir string, volumeName string) error {
|
|
|
|
return doCleanSubPaths(mounter, podDir, volumeName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This implementation is shared between Linux and NsEnterMounter
|
|
|
|
func doCleanSubPaths(mounter Interface, podDir string, volumeName string) error {
|
|
|
|
// scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/*
|
|
|
|
subPathDir := filepath.Join(podDir, containerSubPathDirectoryName, volumeName)
|
2018-03-19 23:21:35 +00:00
|
|
|
glog.V(4).Infof("Cleaning up subpath mounts for %s", subPathDir)
|
|
|
|
|
2018-03-05 08:14:44 +00:00
|
|
|
containerDirs, err := ioutil.ReadDir(subPathDir)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fmt.Errorf("error reading %s: %s", subPathDir, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, containerDir := range containerDirs {
|
|
|
|
if !containerDir.IsDir() {
|
|
|
|
glog.V(4).Infof("Container file is not a directory: %s", containerDir.Name())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
glog.V(4).Infof("Cleaning up subpath mounts for container %s", containerDir.Name())
|
|
|
|
|
|
|
|
// scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/*
|
|
|
|
fullContainerDirPath := filepath.Join(subPathDir, containerDir.Name())
|
|
|
|
subPaths, err := ioutil.ReadDir(fullContainerDirPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error reading %s: %s", fullContainerDirPath, err)
|
|
|
|
}
|
|
|
|
for _, subPath := range subPaths {
|
|
|
|
if err = doCleanSubPath(mounter, fullContainerDirPath, subPath.Name()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Whole container has been processed, remove its directory.
|
|
|
|
if err := os.Remove(fullContainerDirPath); err != nil {
|
|
|
|
return fmt.Errorf("error deleting %s: %s", fullContainerDirPath, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("Removed %s", fullContainerDirPath)
|
|
|
|
}
|
|
|
|
// Whole pod volume subpaths have been cleaned up, remove its subpath directory.
|
|
|
|
if err := os.Remove(subPathDir); err != nil {
|
|
|
|
return fmt.Errorf("error deleting %s: %s", subPathDir, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("Removed %s", subPathDir)
|
|
|
|
|
|
|
|
// Remove entire subpath directory if it's the last one
|
|
|
|
podSubPathDir := filepath.Join(podDir, containerSubPathDirectoryName)
|
|
|
|
if err := os.Remove(podSubPathDir); err != nil && !os.IsExist(err) {
|
|
|
|
return fmt.Errorf("error deleting %s: %s", podSubPathDir, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("Removed %s", podSubPathDir)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// doCleanSubPath tears down the single subpath bind mount
|
|
|
|
func doCleanSubPath(mounter Interface, fullContainerDirPath, subPathIndex string) error {
|
|
|
|
// process /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/<subPathName>
|
|
|
|
glog.V(4).Infof("Cleaning up subpath mounts for subpath %v", subPathIndex)
|
|
|
|
fullSubPath := filepath.Join(fullContainerDirPath, subPathIndex)
|
|
|
|
notMnt, err := IsNotMountPoint(mounter, fullSubPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error checking %s for mount: %s", fullSubPath, err)
|
|
|
|
}
|
|
|
|
// Unmount it
|
|
|
|
if !notMnt {
|
|
|
|
if err = mounter.Unmount(fullSubPath); err != nil {
|
|
|
|
return fmt.Errorf("error unmounting %s: %s", fullSubPath, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("Unmounted %s", fullSubPath)
|
|
|
|
}
|
|
|
|
// Remove it *non*-recursively, just in case there were some hiccups.
|
|
|
|
if err = os.Remove(fullSubPath); err != nil {
|
|
|
|
return fmt.Errorf("error deleting %s: %s", fullSubPath, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("Removed %s", fullSubPath)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// cleanSubPath will teardown the subpath bind mount and any remove any directories if empty
|
|
|
|
func cleanSubPath(mounter Interface, subpath Subpath) error {
|
|
|
|
containerDir := filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName)
|
|
|
|
|
|
|
|
// Clean subdir bindmount
|
|
|
|
if err := doCleanSubPath(mounter, containerDir, strconv.Itoa(subpath.VolumeMountIndex)); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recusively remove directories if empty
|
|
|
|
if err := removeEmptyDirs(subpath.PodDir, containerDir); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeEmptyDirs works backwards from endDir to baseDir and removes each directory
|
|
|
|
// if it is empty. It stops once it encounters a directory that has content
|
|
|
|
func removeEmptyDirs(baseDir, endDir string) error {
|
|
|
|
if !pathWithinBase(endDir, baseDir) {
|
|
|
|
return fmt.Errorf("endDir %q is not within baseDir %q", endDir, baseDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
for curDir := endDir; curDir != baseDir; curDir = filepath.Dir(curDir) {
|
|
|
|
s, err := os.Stat(curDir)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
glog.V(5).Infof("curDir %q doesn't exist, skipping", curDir)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return fmt.Errorf("error stat %q: %v", curDir, err)
|
|
|
|
}
|
|
|
|
if !s.IsDir() {
|
|
|
|
return fmt.Errorf("path %q not a directory", curDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.Remove(curDir)
|
|
|
|
if os.IsExist(err) {
|
|
|
|
glog.V(5).Infof("Directory %q not empty, not removing", curDir)
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return fmt.Errorf("error removing directory %q: %v", curDir, err)
|
|
|
|
}
|
|
|
|
glog.V(5).Infof("Removed directory %q", curDir)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
|
|
|
|
return doSafeMakeDir(pathname, base, perm)
|
|
|
|
}
|
|
|
|
|
2018-04-20 07:26:31 +00:00
|
|
|
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
|
|
|
|
realpath, err := filepath.EvalSymlinks(pathname)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return getMountRefsByDev(mounter, realpath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) {
|
|
|
|
realpath, err := filepath.EvalSymlinks(pathname)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return getFSGroup(realpath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This implementation is shared between Linux and NsEnterMounter
|
|
|
|
func getFSGroup(pathname string) (int64, error) {
|
|
|
|
info, err := os.Stat(pathname)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return int64(info.Sys().(*syscall.Stat_t).Gid), nil
|
|
|
|
}
|
|
|
|
|
2018-03-05 08:14:44 +00:00
|
|
|
// This implementation is shared between Linux and NsEnterMounter
|
|
|
|
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
|
|
|
|
glog.V(4).Infof("Creating directory %q within base %q", pathname, base)
|
|
|
|
|
|
|
|
if !pathWithinBase(pathname, base) {
|
|
|
|
return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Quick check if the directory already exists
|
|
|
|
s, err := os.Stat(pathname)
|
|
|
|
if err == nil {
|
|
|
|
// Path exists
|
|
|
|
if s.IsDir() {
|
|
|
|
// The directory already exists. It can be outside of the parent,
|
|
|
|
// but there is no race-proof check.
|
|
|
|
glog.V(4).Infof("Directory %s already exists", pathname)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find all existing directories
|
|
|
|
existingPath, toCreate, err := findExistingPrefix(base, pathname)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error opening directory %s: %s", pathname, err)
|
|
|
|
}
|
|
|
|
// Ensure the existing directory is inside allowed base
|
|
|
|
fullExistingPath, err := filepath.EvalSymlinks(existingPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error opening directory %s: %s", existingPath, err)
|
|
|
|
}
|
|
|
|
if !pathWithinBase(fullExistingPath, base) {
|
|
|
|
return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
|
|
|
|
parentFD, err := doSafeOpen(fullExistingPath, base)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot open directory %s: %s", existingPath, err)
|
|
|
|
}
|
|
|
|
childFD := -1
|
|
|
|
defer func() {
|
|
|
|
if parentFD != -1 {
|
|
|
|
if err = syscall.Close(parentFD); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if childFD != -1 {
|
|
|
|
if err = syscall.Close(childFD); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", childFD, pathname, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
currentPath := fullExistingPath
|
|
|
|
// create the directories one by one, making sure nobody can change
|
|
|
|
// created directory into symlink.
|
|
|
|
for _, dir := range toCreate {
|
|
|
|
currentPath = filepath.Join(currentPath, dir)
|
|
|
|
glog.V(4).Infof("Creating %s", dir)
|
|
|
|
err = syscall.Mkdirat(parentFD, currentPath, uint32(perm))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
|
|
|
|
}
|
|
|
|
// Dive into the created directory
|
|
|
|
childFD, err := syscall.Openat(parentFD, dir, nofollowFlags, 0)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot open %s: %s", currentPath, err)
|
|
|
|
}
|
|
|
|
// We can be sure that childFD is safe to use. It could be changed
|
|
|
|
// by user after Mkdirat() and before Openat(), however:
|
|
|
|
// - it could not be changed to symlink - we use nofollowFlags
|
|
|
|
// - it could be changed to a file (or device, pipe, socket, ...)
|
|
|
|
// but either subsequent Mkdirat() fails or we mount this file
|
|
|
|
// to user's container. Security is no violated in both cases
|
|
|
|
// and user either gets error or the file that it can already access.
|
|
|
|
|
|
|
|
if err = syscall.Close(parentFD); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
|
|
|
|
}
|
|
|
|
parentFD = childFD
|
|
|
|
childFD = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Everything was created. mkdirat(..., perm) above was affected by current
|
|
|
|
// umask and we must apply the right permissions to the last directory
|
|
|
|
// (that's the one that will be available to the container as subpath)
|
|
|
|
// so user can read/write it. This is the behavior of previous code.
|
|
|
|
// TODO: chmod all created directories, not just the last one.
|
|
|
|
// parentFD is the last created directory.
|
2018-03-16 15:58:47 +00:00
|
|
|
|
|
|
|
// Translate perm (os.FileMode) to uint32 that fchmod() expects
|
|
|
|
kernelPerm := uint32(perm & os.ModePerm)
|
|
|
|
if perm&os.ModeSetgid > 0 {
|
|
|
|
kernelPerm |= syscall.S_ISGID
|
|
|
|
}
|
|
|
|
if perm&os.ModeSetuid > 0 {
|
|
|
|
kernelPerm |= syscall.S_ISUID
|
|
|
|
}
|
|
|
|
if perm&os.ModeSticky > 0 {
|
|
|
|
kernelPerm |= syscall.S_ISVTX
|
|
|
|
}
|
|
|
|
if err = syscall.Fchmod(parentFD, kernelPerm); err != nil {
|
2018-03-05 08:14:44 +00:00
|
|
|
return fmt.Errorf("chmod %q failed: %s", currentPath, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// findExistingPrefix finds prefix of pathname that exists. In addition, it
|
|
|
|
// returns list of remaining directories that don't exist yet.
|
|
|
|
func findExistingPrefix(base, pathname string) (string, []string, error) {
|
|
|
|
rel, err := filepath.Rel(base, pathname)
|
|
|
|
if err != nil {
|
|
|
|
return base, nil, err
|
|
|
|
}
|
|
|
|
dirs := strings.Split(rel, string(filepath.Separator))
|
|
|
|
|
|
|
|
// Do OpenAt in a loop to find the first non-existing dir. Resolve symlinks.
|
|
|
|
// This should be faster than looping through all dirs and calling os.Stat()
|
|
|
|
// on each of them, as the symlinks are resolved only once with OpenAt().
|
|
|
|
currentPath := base
|
|
|
|
fd, err := syscall.Open(currentPath, syscall.O_RDONLY, 0)
|
|
|
|
if err != nil {
|
|
|
|
return pathname, nil, fmt.Errorf("error opening %s: %s", currentPath, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err = syscall.Close(fd); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for i, dir := range dirs {
|
2018-03-22 19:04:39 +00:00
|
|
|
// Using O_PATH here will prevent hangs in case user replaces directory with
|
|
|
|
// fifo
|
|
|
|
childFD, err := syscall.Openat(fd, dir, unix.O_PATH, 0)
|
2018-03-05 08:14:44 +00:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return currentPath, dirs[i:], nil
|
|
|
|
}
|
|
|
|
return base, nil, err
|
|
|
|
}
|
|
|
|
if err = syscall.Close(fd); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
|
|
|
|
}
|
|
|
|
fd = childFD
|
|
|
|
currentPath = filepath.Join(currentPath, dir)
|
|
|
|
}
|
|
|
|
return pathname, []string{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This implementation is shared between Linux and NsEnterMounter
|
|
|
|
// Open path and return its fd.
|
|
|
|
// Symlinks are disallowed (pathname must already resolve symlinks),
|
|
|
|
// and the path must be within the base directory.
|
|
|
|
func doSafeOpen(pathname string, base string) (int, error) {
|
|
|
|
// Calculate segments to follow
|
|
|
|
subpath, err := filepath.Rel(base, pathname)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
segments := strings.Split(subpath, string(filepath.Separator))
|
|
|
|
|
|
|
|
// Assumption: base is the only directory that we have under control.
|
|
|
|
// Base dir is not allowed to be a symlink.
|
|
|
|
parentFD, err := syscall.Open(base, nofollowFlags, 0)
|
|
|
|
if err != nil {
|
|
|
|
return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if parentFD != -1 {
|
|
|
|
if err = syscall.Close(parentFD); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
childFD := -1
|
|
|
|
defer func() {
|
|
|
|
if childFD != -1 {
|
|
|
|
if err = syscall.Close(childFD); err != nil {
|
|
|
|
glog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
currentPath := base
|
|
|
|
|
|
|
|
// Follow the segments one by one using openat() to make
|
|
|
|
// sure the user cannot change already existing directories into symlinks.
|
|
|
|
for _, seg := range segments {
|
|
|
|
currentPath = filepath.Join(currentPath, seg)
|
|
|
|
if !pathWithinBase(currentPath, base) {
|
|
|
|
return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(5).Infof("Opening path %s", currentPath)
|
2018-03-22 19:04:39 +00:00
|
|
|
childFD, err = syscall.Openat(parentFD, seg, openFDFlags, 0)
|
2018-03-05 08:14:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
|
|
|
|
}
|
|
|
|
|
2018-03-22 19:04:39 +00:00
|
|
|
var deviceStat unix.Stat_t
|
|
|
|
err := unix.Fstat(childFD, &deviceStat)
|
|
|
|
if err != nil {
|
|
|
|
return -1, fmt.Errorf("Error running fstat on %s with %v", currentPath, err)
|
|
|
|
}
|
|
|
|
fileFmt := deviceStat.Mode & syscall.S_IFMT
|
|
|
|
if fileFmt == syscall.S_IFLNK {
|
|
|
|
return -1, fmt.Errorf("Unexpected symlink found %s", currentPath)
|
|
|
|
}
|
|
|
|
|
2018-03-05 08:14:44 +00:00
|
|
|
// Close parentFD
|
|
|
|
if err = syscall.Close(parentFD); err != nil {
|
|
|
|
return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
|
|
|
|
}
|
|
|
|
// Set child to new parent
|
|
|
|
parentFD = childFD
|
|
|
|
childFD = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// We made it to the end, return this fd, don't close it
|
|
|
|
finalFD := parentFD
|
|
|
|
parentFD = -1
|
|
|
|
|
|
|
|
return finalFD, nil
|
|
|
|
}
|