github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/storage/looputil/loop.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package looputil
     5  
     6  import (
     7  	"bytes"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  )
    18  
    19  var logger = loggo.GetLogger("juju.storage.looputil")
    20  
    21  // LoopDeviceManager is an API for dealing with storage loop devices.
    22  type LoopDeviceManager interface {
    23  	// DetachLoopDevices detaches loop devices that are backed by files
    24  	// inside the given root filesystem with the given prefix.
    25  	DetachLoopDevices(rootfs, prefix string) error
    26  }
    27  
    28  type runFunc func(cmd string, args ...string) (string, error)
    29  
    30  type loopDeviceManager struct {
    31  	run   runFunc
    32  	stat  func(string) (os.FileInfo, error)
    33  	inode func(os.FileInfo) uint64
    34  }
    35  
    36  // NewLoopDeviceManager returns a new LoopDeviceManager for dealing
    37  // with storage loop devices on the local machine.
    38  func NewLoopDeviceManager() LoopDeviceManager {
    39  	run := func(cmd string, args ...string) (string, error) {
    40  		out, err := exec.Command(cmd, args...).CombinedOutput()
    41  		out = bytes.TrimSpace(out)
    42  		if err != nil {
    43  			if len(out) > 0 {
    44  				err = errors.Annotatef(err, "failed with %q", out)
    45  			}
    46  			return "", err
    47  		}
    48  		return string(out), nil
    49  	}
    50  	return &loopDeviceManager{run, os.Stat, fileInode}
    51  }
    52  
    53  // DetachLoopDevices detaches loop devices that are backed by files
    54  // inside the given root filesystem with the given prefix.
    55  func (m *loopDeviceManager) DetachLoopDevices(rootfs, prefix string) error {
    56  	logger.Debugf("detaching loop devices inside %q", rootfs)
    57  	loopDevices, err := loopDevices(m.run)
    58  	if err != nil {
    59  		return errors.Annotate(err, "listing loop devices")
    60  	}
    61  
    62  	for _, info := range loopDevices {
    63  		logger.Debugf("checking loop device: %v", info)
    64  		if !strings.HasPrefix(info.backingFile, prefix) {
    65  			continue
    66  		}
    67  		if info.backingInode == 0 {
    68  			continue
    69  		}
    70  		rootedBackingFile := path.Join(rootfs, info.backingFile)
    71  		st, err := m.stat(rootedBackingFile)
    72  		if os.IsNotExist(err) {
    73  			continue
    74  		} else if err != nil {
    75  			return errors.Annotate(err, "querying backing file")
    76  		}
    77  		if m.inode(st) != info.backingInode {
    78  			continue
    79  		}
    80  		logger.Debugf("detaching loop device %q", info.name)
    81  		if err := detachLoopDevice(m.run, info.name); err != nil {
    82  			return errors.Trace(err)
    83  		}
    84  	}
    85  	return nil
    86  }
    87  
    88  type loopDeviceInfo struct {
    89  	name         string
    90  	backingFile  string
    91  	backingInode uint64
    92  }
    93  
    94  func loopDevices(run runFunc) ([]loopDeviceInfo, error) {
    95  	out, err := run("losetup", "-a")
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	if out == "" {
   100  		return nil, nil
   101  	}
   102  	lines := strings.Split(out, "\n")
   103  	devices := make([]loopDeviceInfo, len(lines))
   104  	for i, line := range lines {
   105  		info, err := parseLoopDeviceInfo(strings.TrimSpace(line))
   106  		if err != nil {
   107  			return nil, errors.Trace(err)
   108  		}
   109  		devices[i] = info
   110  	}
   111  	return devices, nil
   112  }
   113  
   114  // e.g. "/dev/loop0: [0021]:7504142 (/tmp/test.dat)"
   115  //      "/dev/loop0: [002f]:7504142 (/tmp/test.dat (deleted))"
   116  //      "/dev/loop0: []: (/var/lib/lxc-btrfs.img)"
   117  var loopDeviceInfoRegexp = regexp.MustCompile(`^([^ ]+): \[[[:xdigit:]]*\]:(\d*) \((.*?)(?: \(.*\))?\)$`)
   118  
   119  func parseLoopDeviceInfo(line string) (loopDeviceInfo, error) {
   120  	submatch := loopDeviceInfoRegexp.FindStringSubmatch(line)
   121  	if submatch == nil {
   122  		return loopDeviceInfo{}, errors.Errorf("cannot parse loop device info from %q", line)
   123  	}
   124  	name := submatch[1]
   125  	backingFile := submatch[3]
   126  	var (
   127  		backingInode uint64
   128  		err          error
   129  	)
   130  	if submatch[2] != "" {
   131  		backingInode, err = strconv.ParseUint(submatch[2], 10, 64)
   132  		if err != nil {
   133  			return loopDeviceInfo{}, errors.Annotate(err, "parsing inode")
   134  		}
   135  	}
   136  	return loopDeviceInfo{name, backingFile, backingInode}, nil
   137  }
   138  
   139  func detachLoopDevice(run runFunc, deviceName string) error {
   140  	if _, err := run("losetup", "-d", deviceName); err != nil {
   141  		return errors.Annotatef(err, "detaching loop device %q", deviceName)
   142  	}
   143  	return nil
   144  }