github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  		rootedBackingFile := path.Join(rootfs, info.backingFile)
    68  		st, err := m.stat(rootedBackingFile)
    69  		if os.IsNotExist(err) {
    70  			continue
    71  		} else if err != nil {
    72  			return errors.Annotate(err, "querying backing file")
    73  		}
    74  		if m.inode(st) != info.backingInode {
    75  			continue
    76  		}
    77  		logger.Debugf("detaching loop device %q", info.name)
    78  		if err := detachLoopDevice(m.run, info.name); err != nil {
    79  			return errors.Trace(err)
    80  		}
    81  	}
    82  	return nil
    83  }
    84  
    85  type loopDeviceInfo struct {
    86  	name         string
    87  	backingFile  string
    88  	backingInode uint64
    89  }
    90  
    91  func loopDevices(run runFunc) ([]loopDeviceInfo, error) {
    92  	out, err := run("losetup", "-a")
    93  	if err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  	if out == "" {
    97  		return nil, nil
    98  	}
    99  	lines := strings.Split(out, "\n")
   100  	devices := make([]loopDeviceInfo, len(lines))
   101  	for i, line := range lines {
   102  		info, err := parseLoopDeviceInfo(strings.TrimSpace(line))
   103  		if err != nil {
   104  			return nil, errors.Trace(err)
   105  		}
   106  		devices[i] = info
   107  	}
   108  	return devices, nil
   109  }
   110  
   111  // e.g. "/dev/loop0: [0021]:7504142 (/tmp/test.dat)"
   112  //      "/dev/loop0: [002f]:7504142 (/tmp/test.dat (deleted))"
   113  var loopDeviceInfoRegexp = regexp.MustCompile(`^([^ ]+): \[[[:xdigit:]]+\]:(\d+) \((.*?)(?: \(.*\))?\)$`)
   114  
   115  func parseLoopDeviceInfo(line string) (loopDeviceInfo, error) {
   116  	submatch := loopDeviceInfoRegexp.FindStringSubmatch(line)
   117  	if submatch == nil {
   118  		return loopDeviceInfo{}, errors.Errorf("cannot parse loop device info from %q", line)
   119  	}
   120  	name := submatch[1]
   121  	backingFile := submatch[3]
   122  	backingInode, err := strconv.ParseUint(submatch[2], 10, 64)
   123  	if err != nil {
   124  		return loopDeviceInfo{}, errors.Annotate(err, "parsing inode")
   125  	}
   126  	return loopDeviceInfo{name, backingFile, backingInode}, nil
   127  }
   128  
   129  func detachLoopDevice(run runFunc, deviceName string) error {
   130  	if _, err := run("losetup", "-d", deviceName); err != nil {
   131  		return errors.Annotatef(err, "detaching loop device %q", deviceName)
   132  	}
   133  	return nil
   134  }