...

Source file src/k8s.io/kubernetes/pkg/volume/util/hostutil/hostutil_linux.go

Documentation: k8s.io/kubernetes/pkg/volume/util/hostutil

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2014 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package hostutil
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"strings"
    28  	"syscall"
    29  
    30  	"github.com/opencontainers/selinux/go-selinux"
    31  	"golang.org/x/sys/unix"
    32  	"k8s.io/klog/v2"
    33  	"k8s.io/mount-utils"
    34  	utilpath "k8s.io/utils/path"
    35  )
    36  
    37  const (
    38  	// Location of the mountinfo file
    39  	procMountInfoPath = "/proc/self/mountinfo"
    40  )
    41  
    42  // HostUtil implements HostUtils for Linux platforms.
    43  type HostUtil struct {
    44  }
    45  
    46  // NewHostUtil returns a struct that implements the HostUtils interface on
    47  // linux platforms
    48  func NewHostUtil() *HostUtil {
    49  	return &HostUtil{}
    50  }
    51  
    52  // DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
    53  // If pathname is not a device, log and return false with nil error.
    54  // If open returns errno EBUSY, return true with nil error.
    55  // If open returns nil, return false with nil error.
    56  // Otherwise, return false with error
    57  func (hu *HostUtil) DeviceOpened(pathname string) (bool, error) {
    58  	return ExclusiveOpenFailsOnDevice(pathname)
    59  }
    60  
    61  // PathIsDevice uses FileInfo returned from os.Stat to check if path refers
    62  // to a device.
    63  func (hu *HostUtil) PathIsDevice(pathname string) (bool, error) {
    64  	pathType, err := hu.GetFileType(pathname)
    65  	isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
    66  	return isDevice, err
    67  }
    68  
    69  // ExclusiveOpenFailsOnDevice is shared with NsEnterMounter
    70  func ExclusiveOpenFailsOnDevice(pathname string) (bool, error) {
    71  	var isDevice bool
    72  	finfo, err := os.Stat(pathname)
    73  	if os.IsNotExist(err) {
    74  		isDevice = false
    75  	}
    76  	// err in call to os.Stat
    77  	if err != nil {
    78  		return false, fmt.Errorf(
    79  			"PathIsDevice failed for path %q: %v",
    80  			pathname,
    81  			err)
    82  	}
    83  	// path refers to a device
    84  	if finfo.Mode()&os.ModeDevice != 0 {
    85  		isDevice = true
    86  	}
    87  
    88  	if !isDevice {
    89  		klog.Errorf("Path %q is not referring to a device.", pathname)
    90  		return false, nil
    91  	}
    92  	fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL|unix.O_CLOEXEC, 0)
    93  	// If the device is in use, open will return an invalid fd.
    94  	// When this happens, it is expected that Close will fail and throw an error.
    95  	defer unix.Close(fd)
    96  	if errno == nil {
    97  		// device not in use
    98  		return false, nil
    99  	} else if errno == unix.EBUSY {
   100  		// device is in use
   101  		return true, nil
   102  	}
   103  	// error during call to Open
   104  	return false, errno
   105  }
   106  
   107  // GetDeviceNameFromMount given a mount point, find the device name from its global mount point
   108  func (hu *HostUtil) GetDeviceNameFromMount(mounter mount.Interface, mountPath, pluginMountDir string) (string, error) {
   109  	return getDeviceNameFromMount(mounter, mountPath, pluginMountDir)
   110  }
   111  
   112  // getDeviceNameFromMount find the device name from /proc/self/mountinfo in which
   113  // the mount path reference should match the given plugin mount directory. In case no mount path reference
   114  // matches, returns the volume name taken from its given mountPath
   115  func getDeviceNameFromMount(mounter mount.Interface, mountPath, pluginMountDir string) (string, error) {
   116  	refs, err := mounter.GetMountRefs(mountPath)
   117  	if err != nil {
   118  		klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
   119  		return "", err
   120  	}
   121  	if len(refs) == 0 {
   122  		klog.V(4).Infof("Directory %s is not mounted", mountPath)
   123  		return "", fmt.Errorf("directory %s is not mounted", mountPath)
   124  	}
   125  	for _, ref := range refs {
   126  		if strings.HasPrefix(ref, pluginMountDir) {
   127  			volumeID, err := filepath.Rel(pluginMountDir, ref)
   128  			if err != nil {
   129  				klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
   130  				return "", err
   131  			}
   132  			return volumeID, nil
   133  		}
   134  	}
   135  
   136  	return path.Base(mountPath), nil
   137  }
   138  
   139  // MakeRShared checks that given path is on a mount with 'rshared' mount
   140  // propagation. If not, it bind-mounts the path as rshared.
   141  func (hu *HostUtil) MakeRShared(path string) error {
   142  	return DoMakeRShared(path, procMountInfoPath)
   143  }
   144  
   145  // GetFileType checks for file/directory/socket/block/character devices.
   146  func (hu *HostUtil) GetFileType(pathname string) (FileType, error) {
   147  	return getFileType(pathname)
   148  }
   149  
   150  // PathExists tests if the given path already exists
   151  // Error is returned on any other error than "file not found".
   152  func (hu *HostUtil) PathExists(pathname string) (bool, error) {
   153  	return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
   154  }
   155  
   156  // EvalHostSymlinks returns the path name after evaluating symlinks.
   157  // TODO once the nsenter implementation is removed, this method can be removed
   158  // from the interface and filepath.EvalSymlinks used directly
   159  func (hu *HostUtil) EvalHostSymlinks(pathname string) (string, error) {
   160  	return filepath.EvalSymlinks(pathname)
   161  }
   162  
   163  // FindMountInfo returns the mount info on the given path.
   164  func (hu *HostUtil) FindMountInfo(path string) (mount.MountInfo, error) {
   165  	return findMountInfo(path, procMountInfoPath)
   166  }
   167  
   168  // isShared returns true, if given path is on a mount point that has shared
   169  // mount propagation.
   170  func isShared(mount string, mountInfoPath string) (bool, error) {
   171  	info, err := findMountInfo(mount, mountInfoPath)
   172  	if err != nil {
   173  		return false, err
   174  	}
   175  
   176  	// parse optional parameters
   177  	for _, opt := range info.OptionalFields {
   178  		if strings.HasPrefix(opt, "shared:") {
   179  			return true, nil
   180  		}
   181  	}
   182  	return false, nil
   183  }
   184  
   185  func findMountInfo(path, mountInfoPath string) (mount.MountInfo, error) {
   186  	infos, err := mount.ParseMountInfo(mountInfoPath)
   187  	if err != nil {
   188  		return mount.MountInfo{}, err
   189  	}
   190  
   191  	// process /proc/xxx/mountinfo in backward order and find the first mount
   192  	// point that is prefix of 'path' - that's the mount where path resides
   193  	var info *mount.MountInfo
   194  	for i := len(infos) - 1; i >= 0; i-- {
   195  		if mount.PathWithinBase(path, infos[i].MountPoint) {
   196  			info = &infos[i]
   197  			break
   198  		}
   199  	}
   200  	if info == nil {
   201  		return mount.MountInfo{}, fmt.Errorf("cannot find mount point for %q", path)
   202  	}
   203  	return *info, nil
   204  }
   205  
   206  // DoMakeRShared is common implementation of MakeRShared on Linux. It checks if
   207  // path is shared and bind-mounts it as rshared if needed. mountCmd and
   208  // mountArgs are expected to contain mount-like command, DoMakeRShared will add
   209  // '--bind <path> <path>' and '--make-rshared <path>' to mountArgs.
   210  func DoMakeRShared(path string, mountInfoFilename string) error {
   211  	shared, err := isShared(path, mountInfoFilename)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	if shared {
   216  		klog.V(4).Infof("Directory %s is already on a shared mount", path)
   217  		return nil
   218  	}
   219  
   220  	klog.V(2).Infof("Bind-mounting %q with shared mount propagation", path)
   221  	// mount --bind /var/lib/kubelet /var/lib/kubelet
   222  	if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_BIND, "" /*data*/); err != nil {
   223  		return fmt.Errorf("failed to bind-mount %s: %v", path, err)
   224  	}
   225  
   226  	// mount --make-rshared /var/lib/kubelet
   227  	if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_SHARED|syscall.MS_REC, "" /*data*/); err != nil {
   228  		return fmt.Errorf("failed to make %s rshared: %v", path, err)
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // selinux.SELinuxEnabled implementation for unit tests
   235  type seLinuxEnabledFunc func() bool
   236  
   237  // GetSELinux is common implementation of GetSELinuxSupport on Linux.
   238  func GetSELinux(path string, mountInfoFilename string, selinuxEnabled seLinuxEnabledFunc) (bool, error) {
   239  	// Skip /proc/mounts parsing if SELinux is disabled.
   240  	if !selinuxEnabled() {
   241  		return false, nil
   242  	}
   243  
   244  	info, err := findMountInfo(path, mountInfoFilename)
   245  	if err != nil {
   246  		return false, err
   247  	}
   248  
   249  	// "seclabel" can be both in mount options and super options.
   250  	for _, opt := range info.SuperOptions {
   251  		if opt == "seclabel" {
   252  			return true, nil
   253  		}
   254  	}
   255  	for _, opt := range info.MountOptions {
   256  		if opt == "seclabel" {
   257  			return true, nil
   258  		}
   259  	}
   260  	return false, nil
   261  }
   262  
   263  // GetSELinuxSupport returns true if given path is on a mount that supports
   264  // SELinux.
   265  func (hu *HostUtil) GetSELinuxSupport(pathname string) (bool, error) {
   266  	return GetSELinux(pathname, procMountInfoPath, selinux.GetEnabled)
   267  }
   268  
   269  // GetOwner returns the integer ID for the user and group of the given path
   270  func (hu *HostUtil) GetOwner(pathname string) (int64, int64, error) {
   271  	realpath, err := filepath.EvalSymlinks(pathname)
   272  	if err != nil {
   273  		return -1, -1, err
   274  	}
   275  	return GetOwnerLinux(realpath)
   276  }
   277  
   278  // GetMode returns permissions of the path.
   279  func (hu *HostUtil) GetMode(pathname string) (os.FileMode, error) {
   280  	return GetModeLinux(pathname)
   281  }
   282  
   283  // GetOwnerLinux is shared between Linux and NsEnterMounter
   284  // pathname must already be evaluated for symlinks
   285  func GetOwnerLinux(pathname string) (int64, int64, error) {
   286  	info, err := os.Stat(pathname)
   287  	if err != nil {
   288  		return -1, -1, err
   289  	}
   290  	stat := info.Sys().(*syscall.Stat_t)
   291  	return int64(stat.Uid), int64(stat.Gid), nil
   292  }
   293  
   294  // GetModeLinux is shared between Linux and NsEnterMounter
   295  func GetModeLinux(pathname string) (os.FileMode, error) {
   296  	info, err := os.Stat(pathname)
   297  	if err != nil {
   298  		return 0, err
   299  	}
   300  	return info.Mode(), nil
   301  }
   302  
   303  // GetSELinuxMountContext returns value of -o context=XYZ mount option on
   304  // given mount point.
   305  func (hu *HostUtil) GetSELinuxMountContext(pathname string) (string, error) {
   306  	return getSELinuxMountContext(pathname, procMountInfoPath, selinux.GetEnabled)
   307  }
   308  
   309  // getSELinux is common implementation of GetSELinuxSupport on Linux.
   310  // Using an extra function for unit tests.
   311  func getSELinuxMountContext(path string, mountInfoFilename string, selinuxEnabled seLinuxEnabledFunc) (string, error) {
   312  	// Skip /proc/mounts parsing if SELinux is disabled.
   313  	if !selinuxEnabled() {
   314  		return "", nil
   315  	}
   316  
   317  	info, err := findMountInfo(path, mountInfoFilename)
   318  	if err != nil {
   319  		return "", err
   320  	}
   321  
   322  	for _, opt := range info.SuperOptions {
   323  		if !strings.HasPrefix(opt, "context=") {
   324  			continue
   325  		}
   326  		// Remove context=
   327  		context := strings.TrimPrefix(opt, "context=")
   328  		// Remove double quotes
   329  		context = strings.Trim(context, "\"")
   330  		return context, nil
   331  	}
   332  	return "", nil
   333  }
   334  

View as plain text