k8s.io/kubernetes@v1.29.3/pkg/volume/util/volumepathhandler/volume_path_handler_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2018 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 volumepathhandler 21 22 import ( 23 "errors" 24 "fmt" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 30 "golang.org/x/sys/unix" 31 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/klog/v2" 34 ) 35 36 // AttachFileDevice takes a path to a regular file and makes it available as an 37 // attached block device. 38 func (v VolumePathHandler) AttachFileDevice(path string) (string, error) { 39 blockDevicePath, err := v.GetLoopDevice(path) 40 if err != nil && err.Error() != ErrDeviceNotFound { 41 return "", fmt.Errorf("GetLoopDevice failed for path %s: %v", path, err) 42 } 43 44 // If no existing loop device for the path, create one 45 if blockDevicePath == "" { 46 klog.V(4).Infof("Creating device for path: %s", path) 47 blockDevicePath, err = makeLoopDevice(path) 48 if err != nil { 49 return "", fmt.Errorf("makeLoopDevice failed for path %s: %v", path, err) 50 } 51 } 52 return blockDevicePath, nil 53 } 54 55 // DetachFileDevice takes a path to the attached block device and 56 // detach it from block device. 57 func (v VolumePathHandler) DetachFileDevice(path string) error { 58 loopPath, err := v.GetLoopDevice(path) 59 if err != nil { 60 if err.Error() == ErrDeviceNotFound { 61 klog.Warningf("couldn't find loopback device which takes file descriptor lock. Skip detaching device. device path: %q", path) 62 } else { 63 return fmt.Errorf("GetLoopDevice failed for path %s: %v", path, err) 64 } 65 } else { 66 if len(loopPath) != 0 { 67 err = removeLoopDevice(loopPath) 68 if err != nil { 69 return fmt.Errorf("removeLoopDevice failed for path %s: %v", path, err) 70 } 71 } 72 } 73 return nil 74 } 75 76 // GetLoopDevice returns the full path to the loop device associated with the given path. 77 func (v VolumePathHandler) GetLoopDevice(path string) (string, error) { 78 _, err := os.Stat(path) 79 if os.IsNotExist(err) { 80 return "", errors.New(ErrDeviceNotFound) 81 } 82 if err != nil { 83 return "", fmt.Errorf("not attachable: %v", err) 84 } 85 86 return getLoopDeviceFromSysfs(path) 87 } 88 89 func makeLoopDevice(path string) (string, error) { 90 args := []string{"-f", path} 91 cmd := exec.Command(losetupPath, args...) 92 93 out, err := cmd.CombinedOutput() 94 if err != nil { 95 klog.V(2).Infof("Failed device create command for path: %s %v %s", path, err, out) 96 return "", fmt.Errorf("losetup %s failed: %v", strings.Join(args, " "), err) 97 } 98 99 return getLoopDeviceFromSysfs(path) 100 } 101 102 // removeLoopDevice removes specified loopback device 103 func removeLoopDevice(device string) error { 104 args := []string{"-d", device} 105 cmd := exec.Command(losetupPath, args...) 106 out, err := cmd.CombinedOutput() 107 if err != nil { 108 if _, err := os.Stat(device); os.IsNotExist(err) { 109 return nil 110 } 111 klog.V(2).Infof("Failed to remove loopback device: %s: %v %s", device, err, out) 112 return fmt.Errorf("losetup -d %s failed: %v", device, err) 113 } 114 return nil 115 } 116 117 // getLoopDeviceFromSysfs finds the backing file for a loop 118 // device from sysfs via "/sys/block/loop*/loop/backing_file". 119 func getLoopDeviceFromSysfs(path string) (string, error) { 120 // If the file is a symlink. 121 realPath, err := filepath.EvalSymlinks(path) 122 if err != nil { 123 return "", fmt.Errorf("failed to evaluate path %s: %s", path, err) 124 } 125 126 devices, err := filepath.Glob("/sys/block/loop*") 127 if err != nil { 128 return "", fmt.Errorf("failed to list loop devices in sysfs: %s", err) 129 } 130 131 for _, device := range devices { 132 backingFile := fmt.Sprintf("%s/loop/backing_file", device) 133 134 // The contents of this file is the absolute path of "path". 135 data, err := os.ReadFile(backingFile) 136 if err != nil { 137 continue 138 } 139 140 // Return the first match. 141 backingFilePath := cleanBackingFilePath(string(data)) 142 if backingFilePath == path || backingFilePath == realPath { 143 return fmt.Sprintf("/dev/%s", filepath.Base(device)), nil 144 } 145 } 146 147 return "", errors.New(ErrDeviceNotFound) 148 } 149 150 // cleanPath remove any trailing substrings that are not part of the backing file path. 151 func cleanBackingFilePath(path string) string { 152 // If the block device was deleted, the path will contain a "(deleted)" suffix 153 path = strings.TrimSpace(path) 154 path = strings.TrimSuffix(path, "(deleted)") 155 return strings.TrimSpace(path) 156 } 157 158 // FindGlobalMapPathUUIDFromPod finds {pod uuid} bind mount under globalMapPath 159 // corresponding to map path symlink, and then return global map path with pod uuid. 160 // (See pkg/volume/volume.go for details on a global map path and a pod device map path.) 161 // ex. mapPath symlink: pods/{podUid}}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} -> /dev/sdX 162 // 163 // globalMapPath/{pod uuid} bind mount: plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} -> /dev/sdX 164 func (v VolumePathHandler) FindGlobalMapPathUUIDFromPod(pluginDir, mapPath string, podUID types.UID) (string, error) { 165 var globalMapPathUUID string 166 // Find symbolic link named pod uuid under plugin dir 167 err := filepath.Walk(pluginDir, func(path string, fi os.FileInfo, err error) error { 168 if err != nil { 169 return err 170 } 171 if (fi.Mode()&os.ModeDevice == os.ModeDevice) && (fi.Name() == string(podUID)) { 172 klog.V(5).Infof("FindGlobalMapPathFromPod: path %s, mapPath %s", path, mapPath) 173 if res, err := compareBindMountAndSymlinks(path, mapPath); err == nil && res { 174 globalMapPathUUID = path 175 } 176 } 177 return nil 178 }) 179 if err != nil { 180 return "", fmt.Errorf("FindGlobalMapPathUUIDFromPod failed: %v", err) 181 } 182 klog.V(5).Infof("FindGlobalMapPathFromPod: globalMapPathUUID %s", globalMapPathUUID) 183 // Return path contains global map path + {pod uuid} 184 return globalMapPathUUID, nil 185 } 186 187 // compareBindMountAndSymlinks returns if global path (bind mount) and 188 // pod path (symlink) are pointing to the same device. 189 // If there is an error in checking it returns error. 190 func compareBindMountAndSymlinks(global, pod string) (bool, error) { 191 // To check if bind mount and symlink are pointing to the same device, 192 // we need to check if they are pointing to the devices that have same major/minor number. 193 194 // Get the major/minor number for global path 195 devNumGlobal, err := getDeviceMajorMinor(global) 196 if err != nil { 197 return false, fmt.Errorf("getDeviceMajorMinor failed for path %s: %v", global, err) 198 } 199 200 // Get the symlinked device from the pod path 201 devPod, err := os.Readlink(pod) 202 if err != nil { 203 return false, fmt.Errorf("failed to readlink path %s: %v", pod, err) 204 } 205 // Get the major/minor number for the symlinked device from the pod path 206 devNumPod, err := getDeviceMajorMinor(devPod) 207 if err != nil { 208 return false, fmt.Errorf("getDeviceMajorMinor failed for path %s: %v", devPod, err) 209 } 210 klog.V(5).Infof("CompareBindMountAndSymlinks: devNumGlobal %s, devNumPod %s", devNumGlobal, devNumPod) 211 212 // Check if the major/minor number are the same 213 if devNumGlobal == devNumPod { 214 return true, nil 215 } 216 return false, nil 217 } 218 219 // getDeviceMajorMinor returns major/minor number for the path with below format: 220 // major:minor (in hex) 221 // ex) 222 // 223 // fc:10 224 func getDeviceMajorMinor(path string) (string, error) { 225 var stat unix.Stat_t 226 227 if err := unix.Stat(path, &stat); err != nil { 228 return "", fmt.Errorf("failed to stat path %s: %v", path, err) 229 } 230 231 devNumber := uint64(stat.Rdev) 232 major := unix.Major(devNumber) 233 minor := unix.Minor(devNumber) 234 235 return fmt.Sprintf("%x:%x", major, minor), nil 236 }