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