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 }