/
fuse.go
200 lines (177 loc) · 5.16 KB
/
fuse.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
// cmd/bk/fuse.go
// Copyright(c) 2017 Matt Pharr
// BSD licensed; see LICENSE for details.
package main
// Additional infrastructure to allow accessing backups via FUSE.
import (
"github.com/mmp/bk/storage"
"io/ioutil"
"os"
"strings"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
_ "bazil.org/fuse/fs/fstestutil"
"golang.org/x/net/context"
)
type namedBackup struct {
name string
time time.Time
br *BackupReader
}
// mountFUSE takes the given namedBackups and exports a FUSE filesystem
// where the first three levels of the directory hierarchy are the backup
// name, the yymmdd, and the hhmmss of when the snapshot was taken. Below
// that is the directory hierarchy in the backup.
func mountFUSE(dir string, nb []namedBackup) {
conn, err := fuse.Mount(
dir,
fuse.FSName("bkfs"),
fuse.Subtype("bkfs"),
fuse.VolumeName("backups"),
fuse.ReadOnly(),
)
log.CheckError(err)
defer conn.Close()
root := createPseudoHierarchy(nb)
err = fs.Serve(conn, root)
log.CheckError(err)
<-conn.Ready
if err := conn.MountError; err != nil {
log.CheckError(err)
}
}
// Implements various FUSE interfaces for the top few levels of
// the hierarchy: backup_name/yymmdd/hhmmss.
type pseudoDir struct {
name string
// Each pseudoDir either has 1+ subdirectories in entries, or a non-nil
// *BackupReader (at the node before the actual backup starts).
entries []*pseudoDir
br *BackupReader
}
// Given an array of named backups of the form "backup_name@yyyymmddhhmmss",
// create the corresponding *pseudoDir hierarchy.
func createPseudoHierarchy(nb []namedBackup) *pseudoDir {
var root pseudoDir
for _, b := range nb {
comps := strings.Split(b.name, "@")
log.Check(len(comps) == 2)
log.Check(len(comps[1]) == 14) // yyyymmddhhmmss
dt := comps[1]
comps = []string{comps[0], dt[:4], dt[4:6], dt[6:8], dt[8:]}
pseudoAddRecursive(&root, comps, b)
}
return &root
}
func pseudoAddRecursive(pd *pseudoDir, comps []string, nb namedBackup) {
if len(comps) == 0 {
// Reached the hhmmss directory; below here, it's all provided by
// the BackupReader.
pd.br = nb.br
return
}
// If we already have a pseudoDir for the current path component,
// proceed recursively with it.
for _, e := range pd.entries {
if e.name == comps[0] {
pseudoAddRecursive(e, comps[1:], nb)
return
}
}
// Otherwise add the component to the current pseudoDir and recurse.
pd.entries = append(pd.entries, &pseudoDir{name: comps[0]})
pseudoAddRecursive(pd.entries[len(pd.entries)-1], comps[1:], nb)
}
// Root() should only be called with the root node passed to fs.Serve;
// since pseudoDir also implements the additional Node and Handle
// interfaces for a directory entry, we can just return it directly.
func (pd *pseudoDir) Root() (fs.Node, error) {
return pd, nil
}
func (pd *pseudoDir) Attr(ctx context.Context, a *fuse.Attr) error {
// All pseudorDirs are directories.
a.Mode = os.ModeDir | 0500
return nil
}
// Implements fuse.fs.NodeStringLookuper interface (OMGWTFBBQ naming)
func (pd *pseudoDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
for _, entry := range pd.entries {
if entry.name != name {
continue
}
if entry.br != nil {
// Hand-off to dirEntryBackend for subsequent levels down the
// hierarchy.
return &dirEntryBackend{entry.br.root.Dir, entry.br.backend}, nil
} else {
return entry, nil
}
}
return nil, fuse.ENOENT
}
// Implements fuse.fs.HandleReadDirAller
func (pd *pseudoDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
var de []fuse.Dirent
for _, entry := range pd.entries {
de = append(de, fuse.Dirent{Name: entry.name, Type: fuse.DT_Dir})
}
return de, nil
}
///////////////////////////////////////////////////////////////////////////
// Helper class that agglomerates DirEntry and a storage backend that
// can give us its children.
type dirEntryBackend struct {
DirEntry
backend storage.Backend
}
func (e *dirEntryBackend) Attr(ctx context.Context, a *fuse.Attr) error {
if e.IsFile() {
a.Size = uint64(e.Size)
}
a.Mode = e.Mode
a.Mtime = e.ModTime
return nil
}
// Implements fuse.fs.NodeStringLookuper interface (OMGWTFBBQ naming)
func (e *dirEntryBackend) Lookup(ctx context.Context, name string) (fs.Node, error) {
log.Check(e.IsDir())
entries := readDirEntries(e.Hash, e.backend)
for _, entry := range entries {
if entry.Name == name {
return &dirEntryBackend{entry, e.backend}, nil
}
}
return nil, fuse.ENOENT
}
// Implements fuse.fs.HandleReadDirAller
func (e *dirEntryBackend) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
var dirents []fuse.Dirent
for _, entry := range readDirEntries(e.Hash, e.backend) {
de := fuse.Dirent{Name: entry.Name}
switch {
case e.IsDir():
de.Type = fuse.DT_Dir
case e.IsFile():
de.Type = fuse.DT_File
case e.IsSymLink():
de.Type = fuse.DT_Link
default:
log.Fatal("Unhandled DirEntry type: %+v", e)
}
dirents = append(dirents, de)
}
return dirents, nil
}
// Implements fuse.fs.HandleReadAller
func (e *dirEntryBackend) ReadAll(ctx context.Context) ([]byte, error) {
r, err := e.GetContentsReader(nil, e.backend)
if err != nil {
return nil, err
}
b, err := ioutil.ReadAll(r)
if err != nil {
return b, err
}
return b, r.Close()
}