diff options
Diffstat (limited to 'vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go')
-rw-r--r-- | vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go | 578 |
1 files changed, 423 insertions, 155 deletions
diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go index 4c141ad5b..11835432d 100644 --- a/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go @@ -19,19 +19,21 @@ limitations under the License. package mount import ( - "bufio" + "errors" "fmt" - "hash/fnv" - "io" "os" "os/exec" + "path" + "path/filepath" "strconv" "strings" "syscall" "github.com/golang/glog" + "golang.org/x/sys/unix" "k8s.io/apimachinery/pkg/util/sets" - utilexec "k8s.io/kubernetes/pkg/util/exec" + utilio "k8s.io/kubernetes/pkg/util/io" + utilexec "k8s.io/utils/exec" ) const ( @@ -41,9 +43,8 @@ const ( expectedNumFieldsPerLine = 6 // Location of the mount file to use procMountsPath = "/proc/mounts" -) - -const ( + // Location of the mountinfo file + procMountInfoPath = "/proc/self/mountinfo" // 'fsck' found errors and corrected them fsckErrorsCorrected = 1 // 'fsck' found errors but exited without correcting them @@ -55,77 +56,154 @@ const ( // kubelet is running in the host's root mount namespace. type Mounter struct { mounterPath string + 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(), + } } // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must -// be an emtpy string in case it's not required, e.g. for remount, or for auto filesystem -// type, where kernel handles fs type for you. The mount 'options' is a list of options, +// be an empty string in case it's not required, e.g. for remount, or for auto filesystem +// type, where kernel handles fstype for you. The mount 'options' is a list of options, // 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 { // Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty. - // All Linux distros are expected to be shipped with a mount utility that an support bind mounts. + // All Linux distros are expected to be shipped with a mount utility that a support bind mounts. mounterPath := "" bind, bindRemountOpts := isBind(options) if bind { - err := doMount(mounterPath, defaultMountCommand, source, target, fstype, []string{"bind"}) + err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, []string{"bind"}) if err != nil { return err } - return doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts) + return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts) } // The list of filesystems that require containerized mounter on GCI image cluster fsTypesNeedMounter := sets.NewString("nfs", "glusterfs", "ceph", "cifs") if fsTypesNeedMounter.Has(fstype) { mounterPath = mounter.mounterPath } - return doMount(mounterPath, defaultMountCommand, source, target, fstype, options) -} - -// isBind detects whether a bind mount is being requested and makes the remount options to -// use in case of bind mount, due to the fact that bind mount doesn't respect mount options. -// The list equals: -// options - 'bind' + 'remount' (no duplicate) -func isBind(options []string) (bool, []string) { - bindRemountOpts := []string{"remount"} - bind := false - - if len(options) != 0 { - for _, option := range options { - switch option { - case "bind": - bind = true - break - case "remount": - break - default: - bindRemountOpts = append(bindRemountOpts, option) - } - } - } - - return bind, bindRemountOpts + return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options) } // doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used. -func doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error { +func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error { mountArgs := makeMountArgs(source, target, fstype, options) if len(mounterPath) > 0 { mountArgs = append([]string{mountCmd}, mountArgs...) mountCmd = mounterPath } + if m.withSystemd { + // 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). + mountCmd, mountArgs = addSystemdScope("systemd-run", target, mountCmd, mountArgs) + } 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. + } + glog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs) command := exec.Command(mountCmd, mountArgs...) output, err := command.CombinedOutput() if err != nil { - glog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", err, mountCmd, source, target, fstype, options, string(output)) - return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", - err, mountCmd, source, target, fstype, options, string(output)) + 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)) } return err } +// 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 +} + +// 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 +} + // makeMountArgs makes the arguments to the mount(8) command. func makeMountArgs(source, target, fstype string, options []string) []string { // Build mount command as follows: @@ -145,6 +223,13 @@ func makeMountArgs(source, target, fstype string, options []string) []string { return mountArgs } +// 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...) +} + // Unmount unmounts the target. func (mounter *Mounter) Unmount(target string) error { glog.V(4).Infof("Unmounting %s", target) @@ -181,7 +266,7 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { if err != nil { return true, err } - rootStat, err := os.Lstat(file + "/..") + rootStat, err := os.Lstat(filepath.Dir(strings.TrimSuffix(file, "/"))) if err != nil { return true, err } @@ -205,29 +290,41 @@ func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { // PathIsDevice uses FileInfo returned from os.Stat to check if path refers // to a device. func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { - return pathIsDevice(pathname) + pathType, err := mounter.GetFileType(pathname) + isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev + return isDevice, err } func exclusiveOpenFailsOnDevice(pathname string) (bool, error) { - isDevice, err := pathIsDevice(pathname) + var isDevice bool + finfo, err := os.Stat(pathname) + if os.IsNotExist(err) { + isDevice = false + } + // err in call to os.Stat if err != nil { return false, fmt.Errorf( "PathIsDevice failed for path %q: %v", pathname, err) } + // path refers to a device + if finfo.Mode()&os.ModeDevice != 0 { + isDevice = true + } + if !isDevice { - glog.Errorf("Path %q is not refering to a device.", pathname) + glog.Errorf("Path %q is not referring to a device.", pathname) return false, nil } - fd, errno := syscall.Open(pathname, syscall.O_RDONLY|syscall.O_EXCL, 0) + fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL, 0) // 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. - defer syscall.Close(fd) + defer unix.Close(fd) if errno == nil { // device not in use return false, nil - } else if errno == syscall.EBUSY { + } else if errno == unix.EBUSY { // device is in use return true, nil } @@ -235,121 +332,174 @@ func exclusiveOpenFailsOnDevice(pathname string) (bool, error) { return false, errno } -func pathIsDevice(pathname string) (bool, error) { - finfo, err := os.Stat(pathname) - if os.IsNotExist(err) { - return false, nil - } - // err in call to os.Stat - if err != nil { - return false, err - } - // path refers to a device - if finfo.Mode()&os.ModeDevice != 0 { - return true, nil - } - // path does not refer to device - return false, nil -} - //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) } -func listProcMounts(mountFilePath string) ([]MountPoint, error) { - hash1, err := readProcMounts(mountFilePath, nil) +// 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 { - return nil, err + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err } - - for i := 0; i < maxListTries; i++ { - mps := []MountPoint{} - hash2, err := readProcMounts(mountFilePath, &mps) - if err != nil { - return nil, err - } - if hash1 == hash2 { - // Success - return mps, nil + 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 } - hash1 = hash2 } - return nil, fmt.Errorf("failed to get a consistent snapshot of %v after %d tries", mountFilePath, maxListTries) + + return path.Base(mountPath), nil } -// readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash -// of the contents. If the out argument is not nil, this fills it with MountPoint structs. -func readProcMounts(mountFilePath string, out *[]MountPoint) (uint32, error) { - file, err := os.Open(mountFilePath) +func listProcMounts(mountFilePath string) ([]MountPoint, error) { + content, err := utilio.ConsistentRead(mountFilePath, maxListTries) if err != nil { - return 0, err + return nil, err } - defer file.Close() - return readProcMountsFrom(file, out) + return parseProcMounts(content) } -func readProcMountsFrom(file io.Reader, out *[]MountPoint) (uint32, error) { - hash := fnv.New32a() - scanner := bufio.NewReader(file) - for { - line, err := scanner.ReadString('\n') - if err == io.EOF { - break +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 } fields := strings.Fields(line) if len(fields) != expectedNumFieldsPerLine { - return 0, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) + return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) } - fmt.Fprintf(hash, "%s", line) + mp := MountPoint{ + Device: fields[0], + Path: fields[1], + Type: fields[2], + Opts: strings.Split(fields[3], ","), + } - if out != nil { - mp := MountPoint{ - Device: fields[0], - Path: fields[1], - Type: fields[2], - Opts: strings.Split(fields[3], ","), - } + freq, err := strconv.Atoi(fields[4]) + if err != nil { + return nil, err + } + mp.Freq = freq - freq, err := strconv.Atoi(fields[4]) - if err != nil { - return 0, err - } - mp.Freq = freq + pass, err := strconv.Atoi(fields[5]) + if err != nil { + return nil, err + } + mp.Pass = pass - pass, err := strconv.Atoi(fields[5]) - if err != nil { - return 0, err - } - mp.Pass = pass + out = append(out, mp) + } + return out, nil +} + +func (mounter *Mounter) MakeRShared(path string) error { + return doMakeRShared(path, procMountInfoPath) +} + +func (mounter *Mounter) GetFileType(pathname string) (FileType, error) { + var pathType FileType + finfo, err := os.Stat(pathname) + if os.IsNotExist(err) { + return pathType, fmt.Errorf("path %q does not exist", pathname) + } + // err in call to os.Stat + if err != nil { + return pathType, err + } + + mode := finfo.Sys().(*syscall.Stat_t).Mode + switch mode & syscall.S_IFMT { + case syscall.S_IFSOCK: + return FileTypeSocket, nil + case syscall.S_IFBLK: + return FileTypeBlockDev, nil + case syscall.S_IFCHR: + return FileTypeCharDev, nil + case syscall.S_IFDIR: + return FileTypeDirectory, nil + case syscall.S_IFREG: + return FileTypeFile, nil + } + + return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device") +} + +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 +} - *out = append(*out, mp) +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 hash.Sum32(), nil + return nil +} + +func (mounter *Mounter) ExistsPath(pathname string) bool { + _, err := os.Stat(pathname) + if err != nil { + return false + } + return true } // formatAndMount uses unix utils to format and mount the given disk func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { + readOnly := false + for _, option := range options { + if option == "ro" { + readOnly = true + break + } + } + options = append(options, "defaults") - // Run fsck on the disk to fix repairable issues - glog.V(4).Infof("Checking for issues with fsck on disk: %s", source) - args := []string{"-a", source} - cmd := mounter.Runner.Command("fsck", args...) - out, err := cmd.CombinedOutput() - 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)) + 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)) + } } } @@ -359,13 +509,18 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, if mountErr != nil { // Mount failed. This indicates either that the disk is unformatted or // it contains an unexpected filesystem. - existingFormat, err := mounter.getDiskFormat(source) + existingFormat, err := mounter.GetDiskFormat(source) if err != nil { return err } if existingFormat == "" { + 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") + } + // Disk is unformatted so format it. - args = []string{source} + args := []string{source} // Use 'ext4' as the default if len(fstype) == 0 { fstype = "ext4" @@ -375,8 +530,7 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, args = []string{"-F", source} } glog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args) - cmd := mounter.Runner.Command("mkfs."+fstype, args...) - _, err := cmd.CombinedOutput() + _, err := mounter.Exec.Run("mkfs."+fstype, args...) if err == nil { // the disk has been formatted successfully try to mount it again. glog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target) @@ -398,35 +552,149 @@ func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, return mountErr } -// diskLooksUnformatted uses 'lsblk' to see if the given disk is unformated -func (mounter *SafeFormatAndMount) getDiskFormat(disk string) (string, error) { - args := []string{"-n", "-o", "FSTYPE", disk} - cmd := mounter.Runner.Command("lsblk", args...) - glog.V(4).Infof("Attempting to determine if disk %q is formatted using lsblk with args: (%v)", disk, args) - dataOut, err := cmd.CombinedOutput() +// GetDiskFormat uses 'blkid' to see if the given disk is unformated +func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) { + args := []string{"-p", "-s", "TYPE", "-s", "PTTYPE", "-o", "export", disk} + 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...) output := string(dataOut) - glog.V(4).Infof("Output: %q", output) + glog.V(4).Infof("Output: %q, err: %v", output, err) if err != nil { + 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 + } + } glog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err) return "", err } - // Split lsblk output into lines. Unformatted devices should contain only - // "\n". Beware of "\n\n", that's a device with one empty partition. - output = strings.TrimSuffix(output, "\n") // Avoid last empty line + var fstype, pttype string + lines := strings.Split(output, "\n") - if lines[0] != "" { - // The device is formatted - return lines[0], nil + 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(lines) == 1 { - // The device is unformatted and has no dependent devices - return "", nil + 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 } - // The device has dependent devices, most probably partitions (LVM, LUKS - // and MD RAID are reported as FSTYPE and caught above). - return "unknown data, probably partitions", nil + return fstype, nil +} + +// 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 { + 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 + } + 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{}, + } + 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. +func doMakeRShared(path string, mountInfoFilename string) error { + 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 } |