-
Notifications
You must be signed in to change notification settings - Fork 11
/
send.go
245 lines (239 loc) · 6.05 KB
/
send.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
package btrfs
import (
"fmt"
"io"
"os"
"path/filepath"
"unsafe"
)
func Send(w io.Writer, parent string, subvols ...string) error {
if len(subvols) == 0 {
return nil
}
// use first send subvol to determine mount_root
subvol, err := filepath.Abs(subvols[0])
if err != nil {
return err
}
mountRoot, err := findMountRoot(subvol)
if err == os.ErrNotExist {
return fmt.Errorf("cannot find a mountpoint for %s", subvol)
} else if err != nil {
return err
}
var (
cloneSrc []objectID
parentID objectID
)
if parent != "" {
parent, err = filepath.Abs(parent)
if err != nil {
return err
}
id, err := getPathRootID(parent)
if err != nil {
return fmt.Errorf("cannot get parent root id: %v", err)
}
parentID = id
cloneSrc = append(cloneSrc, id)
}
// check all subvolumes
paths := make([]string, 0, len(subvols))
for _, sub := range subvols {
sub, err = filepath.Abs(sub)
if err != nil {
return err
}
paths = append(paths, sub)
mount, err := findMountRoot(sub)
if err != nil {
return fmt.Errorf("cannot find mount root for %v: %v", sub, err)
} else if mount != mountRoot {
return fmt.Errorf("all subvolumes must be from the same filesystem (%s is not)", sub)
}
ok, err := IsReadOnly(sub)
if err != nil {
return err
} else if !ok {
return fmt.Errorf("subvolume %s is not read-only", sub)
}
}
mfs, err := Open(mountRoot, true)
if err != nil {
return err
}
defer mfs.Close()
full := len(cloneSrc) == 0
for i, sub := range paths {
var rootID objectID
if !full && parent != "" {
rel, err := filepath.Rel(mountRoot, sub)
if err != nil {
return err
}
si, err := subvolSearchByPath(mfs.f, rel)
if err != nil {
return fmt.Errorf("cannot find subvolume %s: %v", rel, err)
}
rootID = objectID(si.RootID)
parentID, err = findGoodParent(mfs.f, rootID, cloneSrc)
if err != nil {
return fmt.Errorf("cannot find good parent for %v: %v", rel, err)
}
}
fs, err := Open(sub, true)
if err != nil {
return err
}
var flags uint64
if i != 0 { // not first
flags |= _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER
}
if i < len(paths)-1 { // not last
flags |= _BTRFS_SEND_FLAG_OMIT_END_CMD
}
err = send(w, fs.f, parentID, cloneSrc, flags)
fs.Close()
if err != nil {
return fmt.Errorf("error sending %s: %v", sub, err)
}
if !full && parent != "" {
cloneSrc = append(cloneSrc, rootID)
}
}
return nil
}
func send(w io.Writer, subvol *os.File, parent objectID, sources []objectID, flags uint64) error {
pr, pw, err := os.Pipe()
if err != nil {
return err
}
errc := make(chan error, 1)
go func() {
defer pr.Close()
_, err := io.Copy(w, pr)
errc <- err
}()
fd := pw.Fd()
wait := func() error {
pw.Close()
return <-errc
}
args := &btrfs_ioctl_send_args{
send_fd: int64(fd),
parent_root: parent,
flags: flags,
}
if len(sources) != 0 {
args.clone_sources = &sources[0]
args.clone_sources_count = uint64(len(sources))
}
if err := iocSend(subvol, args); err != nil {
wait()
return err
}
return wait()
}
// readRootItem reads a root item from the tree.
//
// TODO(dennwc): support older kernels:
// In case we detect a root item smaller then sizeof(root_item),
// we know it's an old version of the root structure and initialize all new fields to zero.
// The same happens if we detect mismatching generation numbers as then we know the root was
// once mounted with an older kernel that was not aware of the root item structure change.
func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) {
sk := btrfs_ioctl_search_key{
tree_id: rootTreeObjectid,
// There may be more than one ROOT_ITEM key if there are
// snapshots pending deletion, we have to loop through them.
min_objectid: rootID,
max_objectid: rootID,
min_type: rootItemKey,
max_type: rootItemKey,
max_offset: maxUint64,
max_transid: maxUint64,
nr_items: 4096,
}
for ; sk.min_offset < maxUint64; sk.min_offset++ {
results, err := treeSearchRaw(mnt, sk)
if err != nil {
return nil, err
} else if len(results) == 0 {
break
}
for _, r := range results {
sk.min_objectid = r.ObjectID
sk.min_type = r.Type
sk.min_offset = r.Offset
if r.ObjectID > rootID {
break
}
if r.ObjectID == rootID && r.Type == rootItemKey {
const sz = int(unsafe.Sizeof(btrfs_root_item_raw{}))
if len(r.Data) > sz {
return nil, fmt.Errorf("btrfs_root_item is larger than expected; kernel is newer than the library")
} else if len(r.Data) < sz { // TODO
return nil, fmt.Errorf("btrfs_root_item is smaller then expected; kernel version is too old")
}
p := asRootItem(r.Data).Decode()
return &p, nil
}
}
results = nil
if sk.min_type != rootItemKey || sk.min_objectid != rootID {
break
}
}
return nil, ErrNotFound
}
func getParent(mnt *os.File, rootID objectID) (*SubvolInfo, error) {
st, err := subvolSearchByRootID(mnt, rootID, "")
if err != nil {
return nil, fmt.Errorf("cannot find subvolume %d to determine parent: %v", rootID, err)
}
return subvolSearchByUUID(mnt, st.ParentUUID)
}
func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectID, error) {
parent, err := getParent(mnt, rootID)
if err != nil {
return 0, fmt.Errorf("get parent failed: %v", err)
}
for _, id := range cloneSrc {
if id == objectID(parent.RootID) {
return objectID(parent.RootID), nil
}
}
var (
bestParent *SubvolInfo
bestDiff uint64 = maxUint64
)
for _, id := range cloneSrc {
parent2, err := getParent(mnt, id)
if err == ErrNotFound {
continue
} else if err != nil {
return 0, err
}
if parent2.RootID != parent.RootID {
continue
}
parent2, err = subvolSearchByRootID(mnt, id, "")
if err != nil {
return 0, err
}
diff := int64(parent2.CTransID - parent.CTransID)
if diff < 0 {
diff = -diff
}
if uint64(diff) < bestDiff {
bestParent, bestDiff = parent2, uint64(diff)
}
}
if bestParent != nil {
return objectID(bestParent.RootID), nil
}
if !parent.ParentUUID.IsZero() {
return findGoodParent(mnt, objectID(parent.RootID), cloneSrc)
}
return 0, ErrNotFound
}