github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/blockdev/blockdev.go (about) 1 /* 2 Copyright 2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package blockdev 18 19 import ( 20 "crypto/sha256" 21 "encoding/binary" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 29 "github.com/Mirantis/virtlet/pkg/utils" 30 "github.com/golang/glog" 31 ) 32 33 const ( 34 // VirtletLogicalDevicePrefix denotes the required prefix for 35 // the virtual block devices created by Virtlet. 36 VirtletLogicalDevicePrefix = "virtlet-dm-" 37 virtletRootfsMagic = 0x263dbe52ba576702 38 virtletRootfsMetadataVersion = 1 39 sectorSize = 512 40 devnameUeventVar = "DEVNAME=" 41 ) 42 43 type virtletRootfsHeader struct { 44 Magic uint64 45 MetadataVersion uint16 46 ImageHash [sha256.Size]byte 47 } 48 49 // LogicalDeviceHandler makes it possible to store metadata in the 50 // first sector of a block device, making the rest of the device 51 // available as another logical device managed by the device mapper. 52 type LogicalDeviceHandler struct { 53 commander utils.Commander 54 devPath string 55 sysfsPath string 56 } 57 58 // NewLogicalDeviceHandler creates a new LogicalDeviceHandler using 59 // the specified commander and paths that should be used in place of 60 // /dev and /sys directories (empty string to use /dev and /sys, 61 // respectively) 62 func NewLogicalDeviceHandler(commander utils.Commander, devPath, sysfsPath string) *LogicalDeviceHandler { 63 if devPath == "" { 64 devPath = "/dev" 65 } 66 if sysfsPath == "" { 67 sysfsPath = "/sys" 68 } 69 return &LogicalDeviceHandler{commander, devPath, sysfsPath} 70 } 71 72 // EnsureDevHeaderMatches returns true if the specified block device 73 // has proper Virtlet header that matches the specified image hash 74 func (ldh *LogicalDeviceHandler) EnsureDevHeaderMatches(devPath string, imageHash [sha256.Size]byte) (bool, error) { 75 f, err := os.OpenFile(devPath, os.O_RDWR|os.O_SYNC, 0) 76 if err != nil { 77 return false, fmt.Errorf("open %q: %v", devPath, err) 78 } 79 defer func() { 80 if f != nil { 81 f.Close() 82 } 83 }() 84 85 var hdr virtletRootfsHeader 86 if err := binary.Read(f, binary.BigEndian, &hdr); err != nil { 87 return false, fmt.Errorf("reading rootfs header: %v", err) 88 } 89 90 headerMatch := true 91 switch { 92 case hdr.Magic != virtletRootfsMagic || hdr.ImageHash != imageHash: 93 headerMatch = false 94 if _, err := f.Seek(0, os.SEEK_SET); err != nil { 95 return false, fmt.Errorf("seek: %v", err) 96 } 97 if err := binary.Write(f, binary.BigEndian, virtletRootfsHeader{ 98 Magic: virtletRootfsMagic, 99 MetadataVersion: virtletRootfsMetadataVersion, 100 ImageHash: imageHash, 101 }); err != nil { 102 return false, fmt.Errorf("writing rootfs header: %v", err) 103 } 104 case hdr.MetadataVersion != virtletRootfsMetadataVersion: 105 // NOTE: we should handle earlier metadata versions 106 // after we introduce new ones. But we can't handle 107 // future metadata versions and any non-matching 108 // metadata versions are future ones currently, so we 109 // don't want to lose any data here. 110 return false, fmt.Errorf("unsupported virtlet root device metadata version %v", hdr.MetadataVersion) 111 } 112 113 if err := f.Close(); err != nil { 114 return false, fmt.Errorf("error closing rootfs device: %v", err) 115 } 116 f = nil 117 return headerMatch, nil 118 } 119 120 // blockDevSizeInSectors returns the size of the block device in sectors 121 func (ldh *LogicalDeviceHandler) blockDevSizeInSectors(devPath string) (uint64, error) { 122 // NOTE: this is also doable via ioctl but this way it's 123 // shorter (no need for fake non-linux impl, extra interface, 124 // extra fake impl for it). Some links that may help if we 125 // decide to use the ioctl later on: 126 // https://github.com/karelzak/util-linux/blob/master/disk-utils/blockdev.c 127 // https://github.com/aicodix/smr/blob/24aa589f378827a69a07d220f114c169693dacec/smr.go#L29 128 out, err := ldh.commander.Command("blockdev", "--getsz", devPath).Run(nil) 129 if err != nil { 130 return 0, err 131 } 132 nSectors, err := strconv.ParseUint(strings.TrimSpace(string(out)), 10, 64) 133 if err != nil { 134 return 0, fmt.Errorf("bad size value returned by blockdev: %q: %v", out, err) 135 } 136 return nSectors, nil 137 } 138 139 // Map maps the device sectors starting from 1 to a new virtual block 140 // device. dmName specifies the name of the new device. 141 func (ldh *LogicalDeviceHandler) Map(devPath, dmName string, imageSize uint64) error { 142 if !strings.HasPrefix(dmName, VirtletLogicalDevicePrefix) { 143 return fmt.Errorf("bad logical device name %q: must have prefix %q", dmName, VirtletLogicalDevicePrefix) 144 } 145 146 nSectors, err := ldh.blockDevSizeInSectors(devPath) 147 if err != nil { 148 return err 149 } 150 151 // sector 0 is reserved for the Virtlet metadata 152 minSectors := (imageSize+sectorSize-1)/sectorSize + 1 153 if nSectors < minSectors { 154 return fmt.Errorf("block device too small for the image: need at least %d bytes (%d sectors) but got %d bytes (%d sectors)", 155 minSectors*sectorSize, 156 minSectors, 157 nSectors*sectorSize, 158 nSectors) 159 } 160 161 hostPath, err := filepath.EvalSymlinks(devPath) 162 if err != nil { 163 return err 164 } 165 166 dmTable := fmt.Sprintf("0 %d linear %s 1\n", nSectors-1, hostPath) 167 _, err = ldh.commander.Command("dmsetup", "create", dmName).Run([]byte(dmTable)) 168 return err 169 } 170 171 // Unmap unmaps the virtual block device 172 func (ldh *LogicalDeviceHandler) Unmap(dmName string) error { 173 _, err := ldh.commander.Command("dmsetup", "remove", dmName).Run(nil) 174 return err 175 } 176 177 // ListVirtletLogicalDevices returns a list of logical devices managed 178 // by Virtlet 179 func (ldh *LogicalDeviceHandler) ListVirtletLogicalDevices() ([]string, error) { 180 table, err := ldh.commander.Command("dmsetup", "table").Run(nil) 181 if err != nil { 182 return nil, fmt.Errorf("dmsetup table: %v", err) 183 } 184 var r []string 185 for _, l := range strings.Split(string(table), "\n") { 186 if l == "" { 187 continue 188 } 189 fields := strings.Fields(l) 190 if len(fields) != 6 || fields[3] != "linear" { 191 continue 192 } 193 virtDevName := fields[0] 194 if strings.HasSuffix(virtDevName, ":") { 195 virtDevName = virtDevName[:len(virtDevName)-1] 196 } 197 198 devID := fields[4] 199 ueventFile := filepath.Join(ldh.sysfsPath, "dev/block", devID, "uevent") 200 ueventContent, err := ioutil.ReadFile(ueventFile) 201 if err != nil { 202 glog.Warningf("error reading %q: %v", ueventFile, err) 203 continue 204 } 205 devName := "" 206 for _, ul := range strings.Split(string(ueventContent), "\n") { 207 ul = strings.TrimSpace(ul) 208 if strings.HasPrefix(ul, devnameUeventVar) { 209 devName = ul[len(devnameUeventVar):] 210 break 211 } 212 } 213 if devName == "" { 214 glog.Warningf("bad uevent file %q: no DEVNAME", ueventFile) 215 continue 216 } 217 218 isVbd, err := ldh.deviceHasVirtletHeader(devName) 219 if err != nil { 220 glog.Warningf("checking device file %q: %v", devName, err) 221 continue 222 } 223 224 if isVbd { 225 r = append(r, virtDevName) 226 } 227 } 228 229 return r, nil 230 } 231 232 func (ldh *LogicalDeviceHandler) deviceHasVirtletHeader(devName string) (bool, error) { 233 f, err := os.Open(filepath.Join(ldh.devPath, devName)) 234 if err != nil { 235 return false, err 236 } 237 defer f.Close() 238 239 var hdr virtletRootfsHeader 240 if err := binary.Read(f, binary.BigEndian, &hdr); err != nil { 241 return false, err 242 } 243 244 return hdr.Magic == virtletRootfsMagic, nil 245 }