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  }