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 }