github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 // values for the TYPE column that we care about 27 28 typeDisk = "disk" 29 typeLoop = "loop" 30 ) 31 32 func init() { 33 DefaultListBlockDevices = listBlockDevices 34 } 35 36 func listBlockDevices() ([]storage.BlockDevice, error) { 37 columns := []string{ 38 "KNAME", // kernel name 39 "SIZE", // size 40 "LABEL", // filesystem label 41 "UUID", // filesystem UUID 42 "FSTYPE", // filesystem type 43 "TYPE", // device type 44 "MOUNTPOINT", // moint point 45 } 46 47 logger.Tracef("executing lsblk") 48 output, err := exec.Command( 49 "lsblk", 50 "-b", // output size in bytes 51 "-P", // output fields as key=value pairs 52 "-o", strings.Join(columns, ","), 53 ).Output() 54 if err != nil { 55 return nil, errors.Annotate( 56 err, "cannot list block devices: lsblk failed", 57 ) 58 } 59 60 blockDeviceMap := make(map[string]storage.BlockDevice) 61 s := bufio.NewScanner(bytes.NewReader(output)) 62 for s.Scan() { 63 pairs := pairsRE.FindAllStringSubmatch(s.Text(), -1) 64 var dev storage.BlockDevice 65 var deviceType string 66 for _, pair := range pairs { 67 switch pair[1] { 68 case "KNAME": 69 dev.DeviceName = pair[2] 70 case "SIZE": 71 size, err := strconv.ParseUint(pair[2], 10, 64) 72 if err != nil { 73 logger.Errorf( 74 "invalid size %q from lsblk: %v", pair[2], err, 75 ) 76 } else { 77 dev.Size = size / bytesInMiB 78 } 79 case "LABEL": 80 dev.Label = pair[2] 81 case "UUID": 82 dev.UUID = pair[2] 83 case "FSTYPE": 84 dev.FilesystemType = pair[2] 85 case "TYPE": 86 deviceType = pair[2] 87 case "MOUNTPOINT": 88 dev.MountPoint = pair[2] 89 default: 90 logger.Debugf("unexpected field from lsblk: %q", pair[1]) 91 } 92 } 93 94 // We may later want to expand this, e.g. to handle lvm, 95 // dmraid, crypt, etc., but this is enough to cover bases 96 // for now. 97 switch deviceType { 98 case typeDisk, typeLoop: 99 default: 100 logger.Tracef("ignoring %q type device: %+v", deviceType, dev) 101 continue 102 } 103 104 // Check if the block device is in use. We need to know this so we can 105 // issue an error if the user attempts to allocate an in-use disk to a 106 // unit. 107 dev.InUse, err = blockDeviceInUse(dev) 108 if os.IsNotExist(err) { 109 // In LXC containers, lsblk will show the block devices of the 110 // host, but the devices will typically not be present. 111 continue 112 } else if err != nil { 113 logger.Errorf( 114 "error checking if %q is in use: %v", dev.DeviceName, err, 115 ) 116 // We cannot detect, so err on the side of caution and default to 117 // "in use" so the device cannot be used. 118 dev.InUse = true 119 } 120 blockDeviceMap[dev.DeviceName] = dev 121 } 122 if err := s.Err(); err != nil { 123 return nil, errors.Annotate(err, "cannot parse lsblk output") 124 } 125 126 blockDevices := make([]storage.BlockDevice, 0, len(blockDeviceMap)) 127 for _, dev := range blockDeviceMap { 128 blockDevices = append(blockDevices, dev) 129 } 130 return blockDevices, nil 131 } 132 133 // blockDeviceInUse checks if the specified block device 134 // is in use by attempting to open the device exclusively. 135 // 136 // If the error returned satisfies os.IsNotExists, then 137 // the device will be ignored altogether. 138 var blockDeviceInUse = func(dev storage.BlockDevice) (bool, error) { 139 f, err := os.OpenFile("/dev/"+dev.DeviceName, os.O_EXCL, 0) 140 if err == nil { 141 f.Close() 142 return false, nil 143 } 144 perr, ok := err.(*os.PathError) 145 if !ok { 146 return false, err 147 } 148 // open(2): "In general, the behavior of O_EXCL is undefined if 149 // it is used without O_CREAT. There is one exception: on Linux 150 // 2.6 and later, O_EXCL can be used without O_CREAT if pathname 151 // refers to a block device. If the block device is in use by the 152 // system (e.g., mounted), open() fails with the error EBUSY." 153 if errno, _ := perr.Err.(syscall.Errno); errno == syscall.EBUSY { 154 return true, nil 155 } 156 return false, err 157 }