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