k8s.io/kubernetes@v1.29.3/pkg/volume/util/hostutil/hostutil_linux.go (about) 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 // getDeviceNameFromMountLinux find the device name from /proc/mounts 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 }