1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
19
"github.com/juju/errors"
21
"github.com/juju/juju/storage"
24
var pairsRE = regexp.MustCompile(`([A-Z]+)=(?:"(.*?)")`)
27
// values for the TYPE column that we care about
34
DefaultListBlockDevices = listBlockDevices
37
func listBlockDevices() ([]storage.BlockDevice, error) {
39
"KNAME", // kernel name
41
"LABEL", // filesystem label
42
"UUID", // filesystem UUID
43
"FSTYPE", // filesystem type
44
"TYPE", // device type
45
"MOUNTPOINT", // moint point
48
logger.Tracef("executing lsblk")
49
output, err := exec.Command(
51
"-b", // output size in bytes
52
"-P", // output fields as key=value pairs
53
"-o", strings.Join(columns, ","),
56
return nil, errors.Annotate(
57
err, "cannot list block devices: lsblk failed",
61
var devices []storage.BlockDevice
62
s := bufio.NewScanner(bytes.NewReader(output))
64
pairs := pairsRE.FindAllStringSubmatch(s.Text(), -1)
65
var dev storage.BlockDevice
67
for _, pair := range pairs {
70
dev.DeviceName = pair[2]
72
size, err := strconv.ParseUint(pair[2], 10, 64)
75
"invalid size %q from lsblk: %v", pair[2], err,
78
dev.Size = size / bytesInMiB
85
dev.FilesystemType = pair[2]
89
dev.MountPoint = pair[2]
91
logger.Debugf("unexpected field from lsblk: %q", pair[1])
95
// We may later want to expand this, e.g. to handle lvm,
96
// dmraid, crypt, etc., but this is enough to cover bases
99
case typeDisk, typeLoop:
101
logger.Tracef("ignoring %q type device: %+v", deviceType, dev)
105
// Check if the block device is in use. We need to know this so we can
106
// issue an error if the user attempts to allocate an in-use disk to a
108
dev.InUse, err = blockDeviceInUse(dev)
109
if os.IsNotExist(err) {
110
// In LXC containers, lsblk will show the block devices of the
111
// host, but the devices will typically not be present.
113
} else if err != nil {
115
"error checking if %q is in use: %v", dev.DeviceName, err,
117
// We cannot detect, so err on the side of caution and default to
118
// "in use" so the device cannot be used.
122
// Add additional information from sysfs.
123
if err := addHardwareInfo(&dev); err != nil {
125
"error getting hardware info for %q from sysfs: %v",
129
devices = append(devices, dev)
131
if err := s.Err(); err != nil {
132
return nil, errors.Annotate(err, "cannot parse lsblk output")
137
// blockDeviceInUse checks if the specified block device
138
// is in use by attempting to open the device exclusively.
140
// If the error returned satisfies os.IsNotExists, then
141
// the device will be ignored altogether.
142
var blockDeviceInUse = func(dev storage.BlockDevice) (bool, error) {
143
f, err := os.OpenFile("/dev/"+dev.DeviceName, os.O_EXCL, 0)
148
perr, ok := err.(*os.PathError)
152
// open(2): "In general, the behavior of O_EXCL is undefined if
153
// it is used without O_CREAT. There is one exception: on Linux
154
// 2.6 and later, O_EXCL can be used without O_CREAT if pathname
155
// refers to a block device. If the block device is in use by the
156
// system (e.g., mounted), open() fails with the error EBUSY."
157
if errno, _ := perr.Err.(syscall.Errno); errno == syscall.EBUSY {
163
// addHardwareInfo adds additional information about the hardware, and how it is
164
// attached to the machine, to the given BlockDevice.
165
func addHardwareInfo(dev *storage.BlockDevice) error {
166
logger.Tracef(`executing "udevadm info" for %s`, dev.DeviceName)
167
output, err := exec.Command(
170
"--path", "/block/"+dev.DeviceName,
173
msg := "udevadm failed"
174
if output := bytes.TrimSpace(output); len(output) > 0 {
175
msg += fmt.Sprintf(" (%s)", output)
177
return errors.Annotate(err, msg)
180
var devpath, idBus, idSerial string
182
s := bufio.NewScanner(bytes.NewReader(output))
185
sep := strings.IndexRune(line, '=')
187
logger.Debugf("unexpected udevadm output line: %q", line)
190
key, value := line[:sep], line[sep+1:]
195
dev.DeviceLinks = strings.Split(value, " ")
201
logger.Tracef("ignoring line: %q", line)
204
if err := s.Err(); err != nil {
205
return errors.Annotate(err, "cannot parse udevadm output")
208
if idBus != "" && idSerial != "" {
209
// ID_BUS will be something like "scsi" or "ata";
210
// ID_SERIAL will be soemthing like ${MODEL}_${SERIALNO};
211
// and together they make up the symlink in /dev/disk/by-id.
212
dev.HardwareId = idBus + "-" + idSerial
215
// For devices on the SCSI bus, we include the address. This is to
216
// support storage providers where the SCSI address may be specified,
217
// but the device name can not (and may change, depending on timing).
218
if idBus == "scsi" && devpath != "" {
219
// DEVPATH will be "<uninteresting stuff>/<SCSI address>/block/<device>".
220
re := regexp.MustCompile(fmt.Sprintf(
221
`^.*/(\d+):(\d+):(\d+):(\d+)/block/%s$`,
222
regexp.QuoteMeta(dev.DeviceName),
224
submatch := re.FindStringSubmatch(devpath)
226
// We use the address scheme used by lshw: bus@address. We don't use
227
// lshw because it does things we don't need, and that slows it down.
229
// In DEVPATH, the address format is "H:C:T:L" ([H]ost, [C]hannel,
230
// [T]arget, [L]un); the lshw address format is "H:C.T.L"
231
dev.BusAddress = fmt.Sprintf(
233
submatch[1], submatch[2], submatch[3], submatch[4],
237
"non matching DEVPATH for %q: %q",
238
dev.DeviceName, devpath,