-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilesystem.go
252 lines (226 loc) · 6.01 KB
/
filesystem.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package docker
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
type Filesystem struct {
RootFS string
RWPath string
// The layers to be mounted on top of each other via aufs.
// Layers are ordered top-to-bottom: the first layer in the list will be mounted on top of the others.
// In other words, THE BASE IMAGE SHOULD BE LAST!
Layers []string
}
func (fs *Filesystem) createMountPoints() error {
if err := os.Mkdir(fs.RootFS, 0755); err != nil && !os.IsExist(err) {
return err
}
if err := os.Mkdir(fs.RWPath, 0755); err != nil && !os.IsExist(err) {
return err
}
return nil
}
func (fs *Filesystem) Mount() error {
if fs.IsMounted() {
return errors.New("Mount: Filesystem already mounted")
}
if err := fs.createMountPoints(); err != nil {
return err
}
rwBranch := fmt.Sprintf("%v=rw", fs.RWPath)
roBranches := ""
for _, layer := range fs.Layers {
roBranches += fmt.Sprintf("%v=ro:", layer)
}
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
if err := mount("none", fs.RootFS, "aufs", 0, branches); err != nil {
return err
}
if !fs.IsMounted() {
return errors.New("Mount failed")
}
return nil
}
func (fs *Filesystem) Umount() error {
if !fs.IsMounted() {
return errors.New("Umount: Filesystem not mounted")
}
if err := syscall.Unmount(fs.RootFS, 0); err != nil {
return err
}
if fs.IsMounted() {
return fmt.Errorf("Umount: Filesystem still mounted after calling umount(%v)", fs.RootFS)
}
// Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
// for some time. We'll just keep retrying until it succeeds.
for retries := 0; retries < 1000; retries++ {
err := os.Remove(fs.RootFS)
if err == nil {
// rm mntpoint succeeded
return nil
}
if os.IsNotExist(err) {
// mntpoint doesn't exist anymore. Success.
return nil
}
// fmt.Printf("(%v) Remove %v returned: %v\n", retries, fs.RootFS, err)
time.Sleep(10 * time.Millisecond)
}
return fmt.Errorf("Umount: Failed to umount %v", fs.RootFS)
}
func (fs *Filesystem) IsMounted() bool {
mntpoint, err := os.Stat(fs.RootFS)
if err != nil {
if os.IsNotExist(err) {
return false
}
panic(err)
}
parent, err := os.Stat(filepath.Join(fs.RootFS, ".."))
if err != nil {
panic(err)
}
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
parentSt := parent.Sys().(*syscall.Stat_t)
return mntpointSt.Dev != parentSt.Dev
}
// Tar returns the contents of the filesystem as an uncompressed tar stream
func (fs *Filesystem) Tar() (io.Reader, error) {
if err := fs.EnsureMounted(); err != nil {
return nil, err
}
return Tar(fs.RootFS)
}
func (fs *Filesystem) EnsureMounted() error {
if !fs.IsMounted() {
if err := fs.Mount(); err != nil {
return err
}
}
return nil
}
type ChangeType int
const (
ChangeModify = iota
ChangeAdd
ChangeDelete
)
type Change struct {
Path string
Kind ChangeType
}
func (change *Change) String() string {
var kind string
switch change.Kind {
case ChangeModify:
kind = "C"
case ChangeAdd:
kind = "A"
case ChangeDelete:
kind = "D"
}
return fmt.Sprintf("%s %s", kind, change.Path)
}
func (fs *Filesystem) Changes() ([]Change, error) {
var changes []Change
err := filepath.Walk(fs.RWPath, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(fs.RWPath, path)
if err != nil {
return err
}
path = filepath.Join("/", path)
// Skip root
if path == "/" {
return nil
}
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
return err
}
change := Change{
Path: path,
}
// Find out what kind of modification happened
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := strings.TrimLeft(file, ".wh.")
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {
// Otherwise, the file was added
change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range fs.Layers {
stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change
return nil
}
}
change.Kind = ChangeModify
break
}
}
}
// Record change
changes = append(changes, change)
return nil
})
if err != nil {
return nil, err
}
return changes, nil
}
// Reset removes all changes to the filesystem, reverting it to its initial state.
func (fs *Filesystem) Reset() error {
if err := os.RemoveAll(fs.RWPath); err != nil {
return err
}
// We removed the RW directory itself along with its content: let's re-create an empty one.
if err := fs.createMountPoints(); err != nil {
return err
}
return nil
}
// Open opens the named file for reading.
func (fs *Filesystem) OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
if err := fs.EnsureMounted(); err != nil {
return nil, err
}
return os.OpenFile(filepath.Join(fs.RootFS, path), flag, perm)
}
// ReadDir reads the directory named by dirname, relative to the Filesystem's root,
// and returns a list of sorted directory entries
func (fs *Filesystem) ReadDir(dirname string) ([]os.FileInfo, error) {
if err := fs.EnsureMounted(); err != nil {
return nil, err
}
return ioutil.ReadDir(filepath.Join(fs.RootFS, dirname))
}
func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {
return &Filesystem{
RootFS: rootfs,
RWPath: rwpath,
Layers: layers,
}
}