github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/diskmanager/lsblk.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // +build linux 5 6 package diskmanager 7 8 import ( 9 "bufio" 10 "bytes" 11 "os" 12 "os/exec" 13 "regexp" 14 "strconv" 15 "strings" 16 "syscall" 17 18 "github.com/juju/errors" 19 20 "github.com/juju/juju/storage" 21 ) 22 23 var pairsRE = regexp.MustCompile(`([A-Z]+)=(?:"(.*?)")`) 24 25 const ( 26 // partitionType is the value of the TYPE column 27 // in lsblk output for partitions. 28 partitionType = "part" 29 ) 30 31 func init() { 32 DefaultListBlockDevices = listBlockDevices 33 } 34 35 func listBlockDevices() ([]storage.BlockDevice, error) { 36 columns := []string{ 37 "KNAME", // kernel name 38 "SIZE", // size 39 "LABEL", // filesystem label 40 "UUID", // filesystem UUID 41 "FSTYPE", // filesystem type 42 "TYPE", // device type 43 } 44 45 logger.Debugf("executing lsblk") 46 output, err := exec.Command( 47 "lsblk", 48 "-b", // output size in bytes 49 "-P", // output fields as key=value pairs 50 "-o", strings.Join(columns, ","), 51 ).Output() 52 if err != nil { 53 return nil, errors.Annotate( 54 err, "cannot list block devices: lsblk failed", 55 ) 56 } 57 58 blockDeviceMap := make(map[string]storage.BlockDevice) 59 s := bufio.NewScanner(bytes.NewReader(output)) 60 for s.Scan() { 61 pairs := pairsRE.FindAllStringSubmatch(s.Text(), -1) 62 var dev storage.BlockDevice 63 var deviceType string 64 for _, pair := range pairs { 65 switch pair[1] { 66 case "KNAME": 67 dev.DeviceName = pair[2] 68 case "SIZE": 69 size, err := strconv.ParseUint(pair[2], 10, 64) 70 if err != nil { 71 logger.Errorf( 72 "invalid size %q from lsblk: %v", pair[2], err, 73 ) 74 } else { 75 dev.Size = size / bytesInMiB 76 } 77 case "LABEL": 78 dev.Label = pair[2] 79 case "UUID": 80 dev.UUID = pair[2] 81 case "FSTYPE": 82 dev.FilesystemType = pair[2] 83 case "TYPE": 84 deviceType = pair[2] 85 default: 86 logger.Debugf("unexpected field from lsblk: %q", pair[1]) 87 } 88 } 89 90 // Partitions may not be used, as there is no guarantee that the 91 // partition will remain available (and we don't model hierarchy). 92 if deviceType == partitionType { 93 logger.Debugf("ignoring partition: %+v", dev) 94 continue 95 } 96 97 // Check if the block device is in use. We need to know this so we can 98 // issue an error if the user attempts to allocate an in-use disk to a 99 // unit. 100 dev.InUse, err = blockDeviceInUse(dev) 101 if os.IsNotExist(err) { 102 // In LXC containers, lsblk will show the block devices of the 103 // host, but the devices will typically not be present. 104 continue 105 } else if err != nil { 106 logger.Errorf( 107 "error checking if %q is in use: %v", dev.DeviceName, err, 108 ) 109 // We cannot detect, so err on the side of caution and default to 110 // "in use" so the device cannot be used. 111 dev.InUse = true 112 } 113 blockDeviceMap[dev.DeviceName] = dev 114 } 115 if err := s.Err(); err != nil { 116 return nil, errors.Annotate(err, "cannot parse lsblk output") 117 } 118 119 blockDevices := make([]storage.BlockDevice, 0, len(blockDeviceMap)) 120 for _, dev := range blockDeviceMap { 121 blockDevices = append(blockDevices, dev) 122 } 123 return blockDevices, nil 124 } 125 126 // blockDeviceInUse checks if the specified block device 127 // is in use by attempting to open the device exclusively. 128 // 129 // If the error returned satisfies os.IsNotExists, then 130 // the device will be ignored altogether. 131 var blockDeviceInUse = func(dev storage.BlockDevice) (bool, error) { 132 f, err := os.OpenFile("/dev/"+dev.DeviceName, os.O_EXCL, 0) 133 if err == nil { 134 f.Close() 135 return false, nil 136 } 137 perr, ok := err.(*os.PathError) 138 if !ok { 139 return false, err 140 } 141 // open(2): "In general, the behavior of O_EXCL is undefined if 142 // it is used without O_CREAT. There is one exception: on Linux 143 // 2.6 and later, O_EXCL can be used without O_CREAT if pathname 144 // refers to a block device. If the block device is in use by the 145 // system (e.g., mounted), open() fails with the error EBUSY." 146 if errno, _ := perr.Err.(syscall.Errno); errno == syscall.EBUSY { 147 return true, nil 148 } 149 return false, err 150 }