github.com/system-transparency/u-root@v6.0.1-0.20190919065413-ed07a650de4c+incompatible/pkg/storage/blockdev.go (about) 1 // Copyright 2017-2019 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package storage 6 7 import ( 8 "bufio" 9 "errors" 10 "fmt" 11 "io/ioutil" 12 "log" 13 "os" 14 "path" 15 "path/filepath" 16 "strconv" 17 "strings" 18 19 "github.com/rekby/gpt" 20 ) 21 22 var ( 23 // LinuxMountsPath is the standard mountpoint list path 24 LinuxMountsPath = "/proc/mounts" 25 ) 26 27 // BlockDev maps a device name to a BlockStat structure for a given block device 28 type BlockDev struct { 29 Name string 30 Stat BlockStat 31 FsUUID string 32 } 33 34 // Summary prints a multiline summary of the BlockDev object 35 // https://www.kernel.org/doc/Documentation/block/stat.txt 36 func (b BlockDev) Summary() string { 37 return fmt.Sprintf(`BlockStat{ 38 Name: %v 39 ReadIOs: %v 40 ReadMerges: %v 41 ReadSectors: %v 42 ReadTicks: %v 43 WriteIOs: %v 44 WriteMerges: %v 45 WriteSectors: %v 46 WriteTicks: %v 47 InFlight: %v 48 IOTicks: %v 49 TimeInQueue: %v 50 }`, 51 b.Name, 52 b.Stat.ReadIOs, 53 b.Stat.ReadMerges, 54 b.Stat.ReadSectors, 55 b.Stat.ReadTicks, 56 b.Stat.WriteIOs, 57 b.Stat.WriteMerges, 58 b.Stat.WriteSectors, 59 b.Stat.WriteTicks, 60 b.Stat.InFlight, 61 b.Stat.IOTicks, 62 b.Stat.TimeInQueue, 63 ) 64 } 65 66 // BlockStat provides block device information as contained in 67 // /sys/class/block/<device_name>/stat 68 type BlockStat struct { 69 ReadIOs uint64 70 ReadMerges uint64 71 ReadSectors uint64 72 ReadTicks uint64 73 WriteIOs uint64 74 WriteMerges uint64 75 WriteSectors uint64 76 WriteTicks uint64 77 InFlight uint64 78 IOTicks uint64 79 TimeInQueue uint64 80 // Kernel 4.18 added four fields for discard tracking, see 81 // https://github.com/torvalds/linux/commit/bdca3c87fb7ad1cc61d231d37eb0d8f90d001e0c 82 DiscardIOs uint64 83 DiscardMerges uint64 84 DiscardSectors uint64 85 DiscardTicks uint64 86 } 87 88 // SystemPartitionGUID is the GUID of EFI system partitions 89 // EFI System partitions have GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B 90 var SystemPartitionGUID = gpt.Guid([...]byte{ 91 0x28, 0x73, 0x2a, 0xc1, 92 0x1f, 0xf8, 93 0xd2, 0x11, 94 0xba, 0x4b, 95 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b, 96 }) 97 98 // BlockStatFromBytes parses a block stat file and returns a BlockStat object. 99 // The format of the block stat file is the one defined by Linux for 100 // /sys/class/block/<device_name>/stat 101 func BlockStatFromBytes(buf []byte) (*BlockStat, error) { 102 fields := strings.Fields(string(buf)) 103 // BlockStat has 11 fields 104 if len(fields) < 11 { 105 return nil, fmt.Errorf("BlockStatFromBytes: parsing %q: got %d fields(%q), want at least 11", buf, len(fields), fields) 106 } 107 intfields := make([]uint64, 0) 108 for _, field := range fields { 109 v, err := strconv.ParseUint(field, 10, 64) 110 if err != nil { 111 return nil, err 112 } 113 intfields = append(intfields, v) 114 } 115 bs := BlockStat{ 116 ReadIOs: intfields[0], 117 ReadMerges: intfields[1], 118 ReadSectors: intfields[2], 119 ReadTicks: intfields[3], 120 WriteIOs: intfields[4], 121 WriteMerges: intfields[5], 122 WriteSectors: intfields[6], 123 WriteTicks: intfields[7], 124 InFlight: intfields[8], 125 IOTicks: intfields[9], 126 TimeInQueue: intfields[10], 127 } 128 if len(fields) >= 15 { 129 bs.DiscardIOs = intfields[11] 130 bs.DiscardMerges = intfields[12] 131 bs.DiscardSectors = intfields[13] 132 bs.DiscardTicks = intfields[14] 133 } 134 return &bs, nil 135 } 136 137 // GetBlockStats iterates over /sys/class/block entries and returns a list of 138 // BlockDev objects, or an error if any 139 func GetBlockStats() ([]BlockDev, error) { 140 blockdevs := make([]BlockDev, 0) 141 devnames := make([]string, 0) 142 root := "/sys/class/block" 143 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 144 if err != nil { 145 return err 146 } 147 rel, err := filepath.Rel(root, path) 148 if err != nil { 149 return err 150 } 151 if rel == "." { 152 return nil 153 } 154 devnames = append(devnames, rel) 155 return nil 156 }) 157 if err != nil { 158 return nil, err 159 } 160 for _, devname := range devnames { 161 fd, err := os.Open(fmt.Sprintf("%s/%s/stat", root, devname)) 162 if err != nil { 163 return nil, err 164 } 165 defer fd.Close() 166 buf, err := ioutil.ReadAll(fd) 167 if err != nil { 168 return nil, err 169 } 170 bstat, err := BlockStatFromBytes(buf) 171 if err != nil { 172 return nil, err 173 } 174 devpath := path.Join("/dev/", devname) 175 uuid := getUUID(devpath) 176 blockdevs = append(blockdevs, BlockDev{Name: devname, Stat: *bstat, FsUUID: uuid}) 177 } 178 return blockdevs, nil 179 } 180 181 func getUUID(devpath string) (fsuuid string) { 182 183 fsuuid = tryVFAT(devpath) 184 if fsuuid != "" { 185 log.Printf("###### FsUUIS in %s: %s", devpath, fsuuid) 186 return fsuuid 187 } 188 fsuuid = tryEXT4(devpath) 189 if fsuuid != "" { 190 log.Printf("###### FsUUIS in %s: %s", devpath, fsuuid) 191 return fsuuid 192 } 193 log.Printf("###### FsUUIS in %s: NONE", devpath) 194 return "" 195 } 196 197 //see https://www.nongnu.org/ext2-doc/ext2.html#DISK-ORGANISATION 198 const ( 199 EXT2SprblkOff = 1024 // Offset of superblock in partition 200 EXT2SprblkSize = 512 // Actually 1024 but most of the last byters are reserved 201 EXT2SprblkMagicOff = 56 // Offset of magic number in suberblock 202 EXT2SprblkMagicSize = 2 203 EXT2SprblkMagic = '\uEF53' // fixed value 204 EXT2SprblkUUIDOff = 104 // Offset of UUID in superblock 205 EXT2SprblkUUIDSize = 16 206 ) 207 208 func tryEXT4(devname string) (uuid string) { 209 log.Printf("try ext4") 210 var off int64 211 212 file, err := os.Open(devname) 213 if err != nil { 214 log.Println(err) 215 return "" 216 } 217 defer file.Close() 218 219 fileinfo, err := file.Stat() 220 if err != nil { 221 log.Println(err) 222 return "" 223 } 224 fmt.Printf("%s %d\n", fileinfo.Name(), fileinfo.Size()) 225 226 // magic number 227 b := make([]byte, EXT2SprblkMagicSize) 228 off = EXT2SprblkOff + EXT2SprblkMagicOff 229 _, err = file.ReadAt(b, off) 230 if err != nil { 231 log.Println(err) 232 return "" 233 } 234 magic := uint16(b[1])<<8 + uint16(b[0]) 235 fmt.Printf("magic: 0x%x\n", magic) 236 if magic != EXT2SprblkMagic { 237 log.Printf("try ext4") 238 return "" 239 } 240 241 // filesystem UUID 242 b = make([]byte, EXT2SprblkUUIDSize) 243 off = EXT2SprblkOff + EXT2SprblkUUIDOff 244 _, err = file.ReadAt(b, off) 245 if err != nil { 246 fmt.Println(err) 247 return "" 248 } 249 uuid = fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 250 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], 251 b[9], b[10], b[11], b[12], b[13], b[14], b[15]) 252 fmt.Printf("UUID=%s\n", uuid) 253 254 return uuid 255 } 256 257 // see https://de.wikipedia.org/wiki/File_Allocation_Table#Aufbau 258 const ( 259 FAT32MagicOff = 82 // Offset of magic number 260 FAT32MagicSize = 8 261 FAT32Magic = "FAT32 " // fixed value 262 FAT32IDOff = 67 // Offset of filesystem-ID / serielnumber. Treated as short filesystem UUID 263 FAT32IDSize = 4 264 ) 265 266 func tryVFAT(devname string) (uuid string) { 267 log.Printf("try vfat") 268 var off int64 269 270 file, err := os.Open(devname) 271 if err != nil { 272 fmt.Println(err) 273 return "" 274 } 275 defer file.Close() 276 277 fileinfo, err := file.Stat() 278 if err != nil { 279 fmt.Println(err) 280 return "" 281 } 282 fmt.Printf("%s %d\n", fileinfo.Name(), fileinfo.Size()) 283 284 // magic number 285 b := make([]byte, FAT32MagicSize) 286 off = 0 + FAT32MagicOff 287 _, err = file.ReadAt(b, off) 288 if err != nil { 289 fmt.Println(err) 290 return "" 291 } 292 magic := string(b) 293 fmt.Printf("magic: %s\n", magic) 294 if magic != FAT32Magic { 295 log.Printf("no vfat") 296 return "" 297 } 298 299 // filesystem UUID 300 b = make([]byte, FAT32IDSize) 301 off = 0 + FAT32IDOff 302 _, err = file.ReadAt(b, off) 303 if err != nil { 304 fmt.Println(err) 305 return "" 306 } 307 uuid = fmt.Sprintf("%02x%02x-%02x%02x", 308 b[3], b[2], b[1], b[0]) 309 fmt.Printf("UUID=%s\n", uuid) 310 311 return uuid 312 } 313 314 // GetGPTTable tries to read a GPT table from the block device described by the 315 // passed BlockDev object, and returns a gpt.Table object, or an error if any 316 func GetGPTTable(device BlockDev) (*gpt.Table, error) { 317 fd, err := os.Open(fmt.Sprintf("/dev/%s", device.Name)) 318 if err != nil { 319 return nil, err 320 } 321 defer fd.Close() 322 if _, err = fd.Seek(512, os.SEEK_SET); err != nil { 323 return nil, err 324 } 325 table, err := gpt.ReadTable(fd, 512) 326 if err != nil { 327 return nil, err 328 } 329 return &table, nil 330 } 331 332 // FilterEFISystemPartitions returns a list of BlockDev objects whose underlying 333 // block device is a valid EFI system partition, or an error if any 334 func FilterEFISystemPartitions(devices []BlockDev) ([]BlockDev, error) { 335 return PartitionsByGUID(devices, SystemPartitionGUID.String()) 336 } 337 338 // PartitionsByGUID returns a list of BlockDev objects whose underlying 339 // block device has the given GUID 340 func PartitionsByGUID(devices []BlockDev, guid string) ([]BlockDev, error) { 341 partitions := make([]BlockDev, 0) 342 for _, device := range devices { 343 table, err := GetGPTTable(device) 344 if err != nil { 345 log.Printf("Skipping %s: %v", device.Name, err) 346 continue 347 } 348 for _, part := range table.Partitions { 349 if part.IsEmpty() { 350 continue 351 } 352 if part.Type.String() == guid { 353 partitions = append(partitions, device) 354 } 355 } 356 } 357 return partitions, nil 358 } 359 360 // PartitionsByFsUUID returns a list of BlockDev objects whose underlying 361 // block device has a filesystem with the given UUID 362 func PartitionsByFsUUID(devices []BlockDev, fsuuid string) []BlockDev { 363 partitions := make([]BlockDev, 0) 364 for _, device := range devices { 365 if device.FsUUID == fsuuid { 366 partitions = append(partitions, device) 367 } 368 } 369 return partitions 370 } 371 372 // GetMountpointByDevice gets the mountpoint by given 373 // device name. Returns on first match 374 func GetMountpointByDevice(devicePath string) (*string, error) { 375 file, err := os.Open(LinuxMountsPath) 376 if err != nil { 377 return nil, err 378 } 379 380 defer file.Close() 381 scanner := bufio.NewScanner(file) 382 383 for scanner.Scan() { 384 deviceInfo := strings.Fields(scanner.Text()) 385 if deviceInfo[0] == devicePath { 386 return &deviceInfo[1], nil 387 } 388 } 389 390 return nil, errors.New("Mountpoint not found") 391 }