~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/lxc/lxd/lxd/storage_btrfs.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package main
 
2
 
 
3
import (
 
4
        "fmt"
 
5
        "io/ioutil"
 
6
        "os"
 
7
        "os/exec"
 
8
        "path"
 
9
        "path/filepath"
 
10
        "strconv"
 
11
        "strings"
 
12
        "syscall"
 
13
 
 
14
        "github.com/gorilla/websocket"
 
15
        "github.com/pborman/uuid"
 
16
 
 
17
        "github.com/lxc/lxd/shared"
 
18
 
 
19
        log "gopkg.in/inconshreveable/log15.v2"
 
20
)
 
21
 
 
22
type storageBtrfs struct {
 
23
        d *Daemon
 
24
 
 
25
        storageShared
 
26
}
 
27
 
 
28
func (s *storageBtrfs) Init(config map[string]interface{}) (storage, error) {
 
29
        s.sType = storageTypeBtrfs
 
30
        s.sTypeName = storageTypeToString(s.sType)
 
31
        if err := s.initShared(); err != nil {
 
32
                return s, err
 
33
        }
 
34
 
 
35
        out, err := exec.LookPath("btrfs")
 
36
        if err != nil || len(out) == 0 {
 
37
                return s, fmt.Errorf("The 'btrfs' tool isn't available")
 
38
        }
 
39
 
 
40
        output, err := exec.Command("btrfs", "version").CombinedOutput()
 
41
        if err != nil {
 
42
                return s, fmt.Errorf("The 'btrfs' tool isn't working properly")
 
43
        }
 
44
 
 
45
        count, err := fmt.Sscanf(strings.SplitN(string(output), " ", 2)[1], "v%s\n", &s.sTypeVersion)
 
46
        if err != nil || count != 1 {
 
47
                return s, fmt.Errorf("The 'btrfs' tool isn't working properly")
 
48
        }
 
49
 
 
50
        return s, nil
 
51
}
 
52
 
 
53
func (s *storageBtrfs) ContainerCreate(container container) error {
 
54
        cPath := container.Path()
 
55
 
 
56
        // MkdirAll the pardir of the BTRFS Subvolume.
 
57
        if err := os.MkdirAll(filepath.Dir(cPath), 0755); err != nil {
 
58
                return err
 
59
        }
 
60
 
 
61
        // Create the BTRFS Subvolume
 
62
        err := s.subvolCreate(cPath)
 
63
        if err != nil {
 
64
                return err
 
65
        }
 
66
 
 
67
        if container.IsPrivileged() {
 
68
                if err := os.Chmod(cPath, 0700); err != nil {
 
69
                        return err
 
70
                }
 
71
        }
 
72
 
 
73
        return container.TemplateApply("create")
 
74
}
 
75
 
 
76
func (s *storageBtrfs) ContainerCreateFromImage(
 
77
        container container, imageFingerprint string) error {
 
78
 
 
79
        imageSubvol := fmt.Sprintf(
 
80
                "%s.btrfs",
 
81
                shared.VarPath("images", imageFingerprint))
 
82
 
 
83
        // Create the btrfs subvol of the image first if it doesn exists.
 
84
        if !shared.PathExists(imageSubvol) {
 
85
                if err := s.ImageCreate(imageFingerprint); err != nil {
 
86
                        return err
 
87
                }
 
88
        }
 
89
 
 
90
        // Now make a snapshot of the image subvol
 
91
        err := s.subvolsSnapshot(imageSubvol, container.Path(), false)
 
92
        if err != nil {
 
93
                return err
 
94
        }
 
95
 
 
96
        if !container.IsPrivileged() {
 
97
                if err = s.shiftRootfs(container); err != nil {
 
98
                        s.ContainerDelete(container)
 
99
                        return err
 
100
                }
 
101
        } else {
 
102
                if err := os.Chmod(container.Path(), 0700); err != nil {
 
103
                        return err
 
104
                }
 
105
        }
 
106
 
 
107
        return container.TemplateApply("create")
 
108
}
 
109
 
 
110
func (s *storageBtrfs) ContainerCanRestore(container container, sourceContainer container) error {
 
111
        return nil
 
112
}
 
113
 
 
114
func (s *storageBtrfs) ContainerDelete(container container) error {
 
115
        cPath := container.Path()
 
116
 
 
117
        // First remove the subvol (if it was one).
 
118
        if s.isSubvolume(cPath) {
 
119
                if err := s.subvolsDelete(cPath); err != nil {
 
120
                        return err
 
121
                }
 
122
        }
 
123
 
 
124
        // Then the directory (if it still exists).
 
125
        err := os.RemoveAll(cPath)
 
126
        if err != nil {
 
127
                s.log.Error("ContainerDelete: failed", log.Ctx{"cPath": cPath, "err": err})
 
128
                return fmt.Errorf("Error cleaning up %s: %s", cPath, err)
 
129
        }
 
130
 
 
131
        return nil
 
132
}
 
133
 
 
134
func (s *storageBtrfs) ContainerCopy(container container, sourceContainer container) error {
 
135
        subvol := sourceContainer.Path()
 
136
        dpath := container.Path()
 
137
 
 
138
        if s.isSubvolume(subvol) {
 
139
                // Snapshot the sourcecontainer
 
140
                err := s.subvolsSnapshot(subvol, dpath, false)
 
141
                if err != nil {
 
142
                        return err
 
143
                }
 
144
        } else {
 
145
                // Create the BTRFS Container.
 
146
                if err := s.ContainerCreate(container); err != nil {
 
147
                        return err
 
148
                }
 
149
 
 
150
                /*
 
151
                 * Copy by using rsync
 
152
                 */
 
153
                output, err := storageRsyncCopy(
 
154
                        sourceContainer.Path(),
 
155
                        container.Path())
 
156
                if err != nil {
 
157
                        s.ContainerDelete(container)
 
158
 
 
159
                        s.log.Error("ContainerCopy: rsync failed", log.Ctx{"output": string(output)})
 
160
                        return fmt.Errorf("rsync failed: %s", string(output))
 
161
                }
 
162
        }
 
163
 
 
164
        if err := s.setUnprivUserAcl(sourceContainer, dpath); err != nil {
 
165
                s.ContainerDelete(container)
 
166
                return err
 
167
        }
 
168
 
 
169
        return container.TemplateApply("copy")
 
170
}
 
171
 
 
172
func (s *storageBtrfs) ContainerStart(container container) error {
 
173
        return nil
 
174
}
 
175
 
 
176
func (s *storageBtrfs) ContainerStop(container container) error {
 
177
        return nil
 
178
}
 
179
 
 
180
func (s *storageBtrfs) ContainerRename(container container, newName string) error {
 
181
        oldName := container.Name()
 
182
        oldPath := container.Path()
 
183
        newPath := containerPath(newName, false)
 
184
 
 
185
        if err := os.Rename(oldPath, newPath); err != nil {
 
186
                return err
 
187
        }
 
188
 
 
189
        if shared.PathExists(shared.VarPath(fmt.Sprintf("snapshots/%s", oldName))) {
 
190
                err := os.Rename(shared.VarPath(fmt.Sprintf("snapshots/%s", oldName)), shared.VarPath(fmt.Sprintf("snapshots/%s", newName)))
 
191
                if err != nil {
 
192
                        return err
 
193
                }
 
194
        }
 
195
 
 
196
        // TODO: No TemplateApply here?
 
197
        return nil
 
198
}
 
199
 
 
200
func (s *storageBtrfs) ContainerRestore(
 
201
        container container, sourceContainer container) error {
 
202
 
 
203
        targetSubVol := container.Path()
 
204
        sourceSubVol := sourceContainer.Path()
 
205
        sourceBackupPath := container.Path() + ".back"
 
206
 
 
207
        // Create a backup of the container
 
208
        err := os.Rename(container.Path(), sourceBackupPath)
 
209
        if err != nil {
 
210
                return err
 
211
        }
 
212
 
 
213
        var failure error
 
214
        if s.isSubvolume(sourceSubVol) {
 
215
                // Restore using btrfs snapshots.
 
216
                err := s.subvolsSnapshot(sourceSubVol, targetSubVol, false)
 
217
                if err != nil {
 
218
                        failure = err
 
219
                }
 
220
        } else {
 
221
                // Restore using rsync but create a btrfs subvol.
 
222
                if err := s.subvolCreate(targetSubVol); err == nil {
 
223
                        output, err := storageRsyncCopy(
 
224
                                sourceSubVol,
 
225
                                targetSubVol)
 
226
 
 
227
                        if err != nil {
 
228
                                s.log.Error(
 
229
                                        "ContainerRestore: rsync failed",
 
230
                                        log.Ctx{"output": string(output)})
 
231
 
 
232
                                failure = err
 
233
                        }
 
234
                } else {
 
235
                        failure = err
 
236
                }
 
237
        }
 
238
 
 
239
        // Now allow unprivileged users to access its data.
 
240
        if err := s.setUnprivUserAcl(sourceContainer, targetSubVol); err != nil {
 
241
                failure = err
 
242
        }
 
243
 
 
244
        if failure != nil {
 
245
                // Restore original container
 
246
                s.ContainerDelete(container)
 
247
                os.Rename(sourceBackupPath, container.Path())
 
248
        } else {
 
249
                // Remove the backup, we made
 
250
                if s.isSubvolume(sourceBackupPath) {
 
251
                        return s.subvolDelete(sourceBackupPath)
 
252
                }
 
253
                os.RemoveAll(sourceBackupPath)
 
254
        }
 
255
 
 
256
        return failure
 
257
}
 
258
 
 
259
func (s *storageBtrfs) ContainerSetQuota(container container, size int64) error {
 
260
        subvol := container.Path()
 
261
 
 
262
        _, err := s.subvolQGroup(subvol)
 
263
        if err != nil {
 
264
                return err
 
265
        }
 
266
 
 
267
        output, err := exec.Command(
 
268
                "btrfs",
 
269
                "qgroup",
 
270
                "limit",
 
271
                "-e", fmt.Sprintf("%d", size),
 
272
                subvol).CombinedOutput()
 
273
 
 
274
        if err != nil {
 
275
                return fmt.Errorf("Failed to set btrfs quota: %s", output)
 
276
        }
 
277
 
 
278
        return nil
 
279
}
 
280
 
 
281
func (s *storageBtrfs) ContainerGetUsage(container container) (int64, error) {
 
282
        return s.subvolQGroupUsage(container.Path())
 
283
}
 
284
 
 
285
func (s *storageBtrfs) ContainerSnapshotCreate(
 
286
        snapshotContainer container, sourceContainer container) error {
 
287
 
 
288
        subvol := sourceContainer.Path()
 
289
        dpath := snapshotContainer.Path()
 
290
 
 
291
        if s.isSubvolume(subvol) {
 
292
                // Create a readonly snapshot of the source.
 
293
                err := s.subvolsSnapshot(subvol, dpath, true)
 
294
                if err != nil {
 
295
                        s.ContainerSnapshotDelete(snapshotContainer)
 
296
                        return err
 
297
                }
 
298
        } else {
 
299
                /*
 
300
                 * Copy by using rsync
 
301
                 */
 
302
                output, err := storageRsyncCopy(
 
303
                        subvol,
 
304
                        dpath)
 
305
                if err != nil {
 
306
                        s.ContainerSnapshotDelete(snapshotContainer)
 
307
 
 
308
                        s.log.Error(
 
309
                                "ContainerSnapshotCreate: rsync failed",
 
310
                                log.Ctx{"output": string(output)})
 
311
                        return fmt.Errorf("rsync failed: %s", string(output))
 
312
                }
 
313
        }
 
314
 
 
315
        return nil
 
316
}
 
317
func (s *storageBtrfs) ContainerSnapshotDelete(
 
318
        snapshotContainer container) error {
 
319
 
 
320
        err := s.ContainerDelete(snapshotContainer)
 
321
        if err != nil {
 
322
                return fmt.Errorf("Error deleting snapshot %s: %s", snapshotContainer.Name(), err)
 
323
        }
 
324
 
 
325
        oldPathParent := filepath.Dir(snapshotContainer.Path())
 
326
        if ok, _ := shared.PathIsEmpty(oldPathParent); ok {
 
327
                os.Remove(oldPathParent)
 
328
        }
 
329
        return nil
 
330
}
 
331
 
 
332
func (s *storageBtrfs) ContainerSnapshotStart(container container) error {
 
333
        if shared.PathExists(container.Path() + ".ro") {
 
334
                return fmt.Errorf("The snapshot is already mounted read-write.")
 
335
        }
 
336
 
 
337
        err := os.Rename(container.Path(), container.Path()+".ro")
 
338
        if err != nil {
 
339
                return err
 
340
        }
 
341
 
 
342
        err = s.subvolsSnapshot(container.Path()+".ro", container.Path(), false)
 
343
        if err != nil {
 
344
                return err
 
345
        }
 
346
 
 
347
        return nil
 
348
}
 
349
 
 
350
func (s *storageBtrfs) ContainerSnapshotStop(container container) error {
 
351
        if !shared.PathExists(container.Path() + ".ro") {
 
352
                return fmt.Errorf("The snapshot isn't currently mounted read-write.")
 
353
        }
 
354
 
 
355
        err := s.subvolsDelete(container.Path())
 
356
        if err != nil {
 
357
                return err
 
358
        }
 
359
 
 
360
        err = os.Rename(container.Path()+".ro", container.Path())
 
361
        if err != nil {
 
362
                return err
 
363
        }
 
364
 
 
365
        return nil
 
366
}
 
367
 
 
368
// ContainerSnapshotRename renames a snapshot of a container.
 
369
func (s *storageBtrfs) ContainerSnapshotRename(
 
370
        snapshotContainer container, newName string) error {
 
371
 
 
372
        oldPath := snapshotContainer.Path()
 
373
        newPath := containerPath(newName, true)
 
374
 
 
375
        // Create the new parent.
 
376
        if !shared.PathExists(filepath.Dir(newPath)) {
 
377
                os.MkdirAll(filepath.Dir(newPath), 0700)
 
378
        }
 
379
 
 
380
        // Now rename the snapshot.
 
381
        if !s.isSubvolume(oldPath) {
 
382
                if err := os.Rename(oldPath, newPath); err != nil {
 
383
                        return err
 
384
                }
 
385
        } else {
 
386
                if err := s.subvolsSnapshot(oldPath, newPath, true); err != nil {
 
387
                        return err
 
388
                }
 
389
                if err := s.subvolsDelete(oldPath); err != nil {
 
390
                        return err
 
391
                }
 
392
        }
 
393
 
 
394
        // Remove the old parent (on container rename) if its empty.
 
395
        if ok, _ := shared.PathIsEmpty(filepath.Dir(oldPath)); ok {
 
396
                os.Remove(filepath.Dir(oldPath))
 
397
        }
 
398
 
 
399
        return nil
 
400
}
 
401
 
 
402
func (s *storageBtrfs) ContainerSnapshotCreateEmpty(snapshotContainer container) error {
 
403
        dpath := snapshotContainer.Path()
 
404
        return s.subvolCreate(dpath)
 
405
}
 
406
 
 
407
func (s *storageBtrfs) ImageCreate(fingerprint string) error {
 
408
        imagePath := shared.VarPath("images", fingerprint)
 
409
        subvol := fmt.Sprintf("%s.btrfs", imagePath)
 
410
 
 
411
        if err := s.subvolCreate(subvol); err != nil {
 
412
                return err
 
413
        }
 
414
 
 
415
        if err := untarImage(imagePath, subvol); err != nil {
 
416
                return err
 
417
        }
 
418
 
 
419
        return nil
 
420
}
 
421
 
 
422
func (s *storageBtrfs) ImageDelete(fingerprint string) error {
 
423
        imagePath := shared.VarPath("images", fingerprint)
 
424
        subvol := fmt.Sprintf("%s.btrfs", imagePath)
 
425
 
 
426
        return s.subvolDelete(subvol)
 
427
}
 
428
 
 
429
func (s *storageBtrfs) subvolCreate(subvol string) error {
 
430
        parentDestPath := filepath.Dir(subvol)
 
431
        if !shared.PathExists(parentDestPath) {
 
432
                if err := os.MkdirAll(parentDestPath, 0700); err != nil {
 
433
                        return err
 
434
                }
 
435
        }
 
436
 
 
437
        output, err := exec.Command(
 
438
                "btrfs",
 
439
                "subvolume",
 
440
                "create",
 
441
                subvol).CombinedOutput()
 
442
        if err != nil {
 
443
                s.log.Debug(
 
444
                        "subvolume create failed",
 
445
                        log.Ctx{"subvol": subvol, "output": string(output)},
 
446
                )
 
447
                return fmt.Errorf(
 
448
                        "btrfs subvolume create failed, subvol=%s, output%s",
 
449
                        subvol,
 
450
                        string(output),
 
451
                )
 
452
        }
 
453
 
 
454
        return nil
 
455
}
 
456
 
 
457
func (s *storageBtrfs) subvolQGroup(subvol string) (string, error) {
 
458
        output, err := exec.Command(
 
459
                "btrfs",
 
460
                "qgroup",
 
461
                "show",
 
462
                subvol,
 
463
                "-e",
 
464
                "-f").CombinedOutput()
 
465
 
 
466
        if err != nil {
 
467
                return "", fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
 
468
        }
 
469
 
 
470
        var qgroup string
 
471
        for _, line := range strings.Split(string(output), "\n") {
 
472
                if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
 
473
                        continue
 
474
                }
 
475
 
 
476
                fields := strings.Fields(line)
 
477
                if len(fields) != 4 {
 
478
                        continue
 
479
                }
 
480
 
 
481
                qgroup = fields[0]
 
482
        }
 
483
 
 
484
        if qgroup == "" {
 
485
                return "", fmt.Errorf("Unable to find quota group")
 
486
        }
 
487
 
 
488
        return qgroup, nil
 
489
}
 
490
 
 
491
func (s *storageBtrfs) subvolQGroupUsage(subvol string) (int64, error) {
 
492
        output, err := exec.Command(
 
493
                "btrfs",
 
494
                "qgroup",
 
495
                "show",
 
496
                subvol,
 
497
                "-e",
 
498
                "-f").CombinedOutput()
 
499
 
 
500
        if err != nil {
 
501
                return -1, fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
 
502
        }
 
503
 
 
504
        for _, line := range strings.Split(string(output), "\n") {
 
505
                if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
 
506
                        continue
 
507
                }
 
508
 
 
509
                fields := strings.Fields(line)
 
510
                if len(fields) != 4 {
 
511
                        continue
 
512
                }
 
513
 
 
514
                usage, err := strconv.ParseInt(fields[2], 10, 64)
 
515
                if err != nil {
 
516
                        continue
 
517
                }
 
518
 
 
519
                return usage, nil
 
520
        }
 
521
 
 
522
        return -1, fmt.Errorf("Unable to find current qgroup usage")
 
523
}
 
524
 
 
525
func (s *storageBtrfs) subvolDelete(subvol string) error {
 
526
        // Attempt (but don't fail on) to delete any qgroup on the subvolume
 
527
        qgroup, err := s.subvolQGroup(subvol)
 
528
        if err == nil {
 
529
                output, err := exec.Command(
 
530
                        "btrfs",
 
531
                        "qgroup",
 
532
                        "destroy",
 
533
                        qgroup,
 
534
                        subvol).CombinedOutput()
 
535
 
 
536
                if err != nil {
 
537
                        s.log.Warn(
 
538
                                "subvolume qgroup delete failed",
 
539
                                log.Ctx{"subvol": subvol, "output": string(output)},
 
540
                        )
 
541
                }
 
542
        }
 
543
 
 
544
        // Delete the subvolume itself
 
545
        output, err := exec.Command(
 
546
                "btrfs",
 
547
                "subvolume",
 
548
                "delete",
 
549
                subvol,
 
550
        ).CombinedOutput()
 
551
 
 
552
        if err != nil {
 
553
                s.log.Warn(
 
554
                        "subvolume delete failed",
 
555
                        log.Ctx{"subvol": subvol, "output": string(output)},
 
556
                )
 
557
        }
 
558
        return nil
 
559
}
 
560
 
 
561
// subvolsDelete is the recursive variant on subvolDelete,
 
562
// it first deletes subvolumes of the subvolume and then the
 
563
// subvolume itself.
 
564
func (s *storageBtrfs) subvolsDelete(subvol string) error {
 
565
        // Delete subsubvols.
 
566
        subsubvols, err := s.getSubVolumes(subvol)
 
567
        if err != nil {
 
568
                return err
 
569
        }
 
570
 
 
571
        for _, subsubvol := range subsubvols {
 
572
                s.log.Debug(
 
573
                        "Deleting subsubvol",
 
574
                        log.Ctx{
 
575
                                "subvol":    subvol,
 
576
                                "subsubvol": subsubvol})
 
577
 
 
578
                if err := s.subvolDelete(path.Join(subvol, subsubvol)); err != nil {
 
579
                        return err
 
580
                }
 
581
        }
 
582
 
 
583
        // Delete the subvol itself
 
584
        if err := s.subvolDelete(subvol); err != nil {
 
585
                return err
 
586
        }
 
587
 
 
588
        return nil
 
589
}
 
590
 
 
591
/*
 
592
 * subvolSnapshot creates a snapshot of "source" to "dest"
 
593
 * the result will be readonly if "readonly" is True.
 
594
 */
 
595
func (s *storageBtrfs) subvolSnapshot(
 
596
        source string, dest string, readonly bool) error {
 
597
 
 
598
        parentDestPath := filepath.Dir(dest)
 
599
        if !shared.PathExists(parentDestPath) {
 
600
                if err := os.MkdirAll(parentDestPath, 0700); err != nil {
 
601
                        return err
 
602
                }
 
603
        }
 
604
 
 
605
        if shared.PathExists(dest) {
 
606
                if err := os.Remove(dest); err != nil {
 
607
                        return err
 
608
                }
 
609
        }
 
610
 
 
611
        var output []byte
 
612
        var err error
 
613
        if readonly {
 
614
                output, err = exec.Command(
 
615
                        "btrfs",
 
616
                        "subvolume",
 
617
                        "snapshot",
 
618
                        "-r",
 
619
                        source,
 
620
                        dest).CombinedOutput()
 
621
        } else {
 
622
                output, err = exec.Command(
 
623
                        "btrfs",
 
624
                        "subvolume",
 
625
                        "snapshot",
 
626
                        source,
 
627
                        dest).CombinedOutput()
 
628
        }
 
629
        if err != nil {
 
630
                s.log.Error(
 
631
                        "subvolume snapshot failed",
 
632
                        log.Ctx{"source": source, "dest": dest, "output": string(output)},
 
633
                )
 
634
                return fmt.Errorf(
 
635
                        "subvolume snapshot failed, source=%s, dest=%s, output=%s",
 
636
                        source,
 
637
                        dest,
 
638
                        string(output),
 
639
                )
 
640
        }
 
641
 
 
642
        return err
 
643
}
 
644
 
 
645
func (s *storageBtrfs) subvolsSnapshot(
 
646
        source string, dest string, readonly bool) error {
 
647
 
 
648
        // Get a list of subvolumes of the root
 
649
        subsubvols, err := s.getSubVolumes(source)
 
650
        if err != nil {
 
651
                return err
 
652
        }
 
653
 
 
654
        if len(subsubvols) > 0 && readonly {
 
655
                // A root with subvolumes can never be readonly,
 
656
                // also don't make subvolumes readonly.
 
657
                readonly = false
 
658
 
 
659
                s.log.Warn(
 
660
                        "Subvolumes detected, ignoring ro flag",
 
661
                        log.Ctx{"source": source, "dest": dest})
 
662
        }
 
663
 
 
664
        // First snapshot the root
 
665
        if err := s.subvolSnapshot(source, dest, readonly); err != nil {
 
666
                return err
 
667
        }
 
668
 
 
669
        // Now snapshot all subvolumes of the root.
 
670
        for _, subsubvol := range subsubvols {
 
671
                if err := s.subvolSnapshot(
 
672
                        path.Join(source, subsubvol),
 
673
                        path.Join(dest, subsubvol),
 
674
                        readonly); err != nil {
 
675
 
 
676
                        return err
 
677
                }
 
678
        }
 
679
 
 
680
        return nil
 
681
}
 
682
 
 
683
/*
 
684
 * isSubvolume returns true if the given Path is a btrfs subvolume
 
685
 * else false.
 
686
 */
 
687
func (s *storageBtrfs) isSubvolume(subvolPath string) bool {
 
688
        if runningInUserns {
 
689
                // subvolume show is restricted to real root, use a workaround
 
690
 
 
691
                fs := syscall.Statfs_t{}
 
692
                err := syscall.Statfs(subvolPath, &fs)
 
693
                if err != nil {
 
694
                        return false
 
695
                }
 
696
 
 
697
                if fs.Type != filesystemSuperMagicBtrfs {
 
698
                        return false
 
699
                }
 
700
 
 
701
                parentFs := syscall.Statfs_t{}
 
702
                err = syscall.Statfs(path.Dir(subvolPath), &parentFs)
 
703
                if err != nil {
 
704
                        return false
 
705
                }
 
706
 
 
707
                if fs.Fsid == parentFs.Fsid {
 
708
                        return false
 
709
                }
 
710
 
 
711
                return true
 
712
        }
 
713
 
 
714
        output, err := exec.Command(
 
715
                "btrfs",
 
716
                "subvolume",
 
717
                "show",
 
718
                subvolPath).CombinedOutput()
 
719
        if err != nil || strings.HasPrefix(string(output), "ERROR: ") {
 
720
                return false
 
721
        }
 
722
 
 
723
        return true
 
724
}
 
725
 
 
726
// getSubVolumes returns a list of relative subvolume paths of "path".
 
727
func (s *storageBtrfs) getSubVolumes(path string) ([]string, error) {
 
728
        result := []string{}
 
729
 
 
730
        if runningInUserns {
 
731
                if !strings.HasSuffix(path, "/") {
 
732
                        path = path + "/"
 
733
                }
 
734
 
 
735
                // Unprivileged users can't get to fs internals
 
736
                filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
 
737
                        if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
 
738
                                return nil
 
739
                        }
 
740
 
 
741
                        if err != nil {
 
742
                                return nil
 
743
                        }
 
744
 
 
745
                        if !fi.IsDir() {
 
746
                                return nil
 
747
                        }
 
748
 
 
749
                        if s.isSubvolume(fpath) {
 
750
                                result = append(result, strings.TrimPrefix(fpath, path))
 
751
                        }
 
752
                        return nil
 
753
                })
 
754
 
 
755
                return result, nil
 
756
        }
 
757
 
 
758
        out, err := exec.Command(
 
759
                "btrfs",
 
760
                "inspect-internal",
 
761
                "rootid",
 
762
                path).CombinedOutput()
 
763
        if err != nil {
 
764
                return result, fmt.Errorf(
 
765
                        "Unable to get btrfs rootid, path='%s', err='%s'",
 
766
                        path,
 
767
                        err)
 
768
        }
 
769
        rootid := strings.TrimRight(string(out), "\n")
 
770
 
 
771
        out, err = exec.Command(
 
772
                "btrfs",
 
773
                "inspect-internal",
 
774
                "subvolid-resolve",
 
775
                rootid, path).CombinedOutput()
 
776
        if err != nil {
 
777
                return result, fmt.Errorf(
 
778
                        "Unable to resolve btrfs rootid, path='%s', err='%s'",
 
779
                        path,
 
780
                        err)
 
781
        }
 
782
        basePath := strings.TrimRight(string(out), "\n")
 
783
 
 
784
        out, err = exec.Command(
 
785
                "btrfs",
 
786
                "subvolume",
 
787
                "list",
 
788
                "-o",
 
789
                path).CombinedOutput()
 
790
        if err != nil {
 
791
                return result, fmt.Errorf(
 
792
                        "Unable to list subvolumes, path='%s', err='%s'",
 
793
                        path,
 
794
                        err)
 
795
        }
 
796
 
 
797
        lines := strings.Split(string(out), "\n")
 
798
        for _, line := range lines {
 
799
                if line == "" {
 
800
                        continue
 
801
                }
 
802
 
 
803
                cols := strings.Fields(line)
 
804
                result = append(result, cols[8][len(basePath):])
 
805
        }
 
806
 
 
807
        return result, nil
 
808
}
 
809
 
 
810
type btrfsMigrationSourceDriver struct {
 
811
        container          container
 
812
        snapshots          []container
 
813
        btrfsSnapshotNames []string
 
814
        btrfs              *storageBtrfs
 
815
        runningSnapName    string
 
816
        stoppedSnapName    string
 
817
}
 
818
 
 
819
func (s *btrfsMigrationSourceDriver) Snapshots() []container {
 
820
        return s.snapshots
 
821
}
 
822
 
 
823
func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string, btrfsParent string) error {
 
824
        args := []string{"send", btrfsPath}
 
825
        if btrfsParent != "" {
 
826
                args = append(args, "-p", btrfsParent)
 
827
        }
 
828
 
 
829
        cmd := exec.Command("btrfs", args...)
 
830
 
 
831
        stdout, err := cmd.StdoutPipe()
 
832
        if err != nil {
 
833
                return err
 
834
        }
 
835
 
 
836
        stderr, err := cmd.StderrPipe()
 
837
        if err != nil {
 
838
                return err
 
839
        }
 
840
 
 
841
        if err := cmd.Start(); err != nil {
 
842
                return err
 
843
        }
 
844
 
 
845
        <-shared.WebsocketSendStream(conn, stdout)
 
846
 
 
847
        output, err := ioutil.ReadAll(stderr)
 
848
        if err != nil {
 
849
                shared.Log.Error("problem reading btrfs send stderr", "err", err)
 
850
        }
 
851
 
 
852
        err = cmd.Wait()
 
853
        if err != nil {
 
854
                shared.Log.Error("problem with btrfs send", "output", string(output))
 
855
        }
 
856
        return err
 
857
}
 
858
 
 
859
func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn) error {
 
860
        if s.container.IsSnapshot() {
 
861
                tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
 
862
                err := os.MkdirAll(tmpPath, 0700)
 
863
                if err != nil {
 
864
                        return err
 
865
                }
 
866
 
 
867
                btrfsPath := fmt.Sprintf("%s/.root", tmpPath)
 
868
                if err := s.btrfs.subvolSnapshot(s.container.Path(), btrfsPath, true); err != nil {
 
869
                        return err
 
870
                }
 
871
 
 
872
                defer s.btrfs.subvolDelete(btrfsPath)
 
873
 
 
874
                return s.send(conn, btrfsPath, "")
 
875
        }
 
876
 
 
877
        for i, snap := range s.snapshots {
 
878
                prev := ""
 
879
                if i > 0 {
 
880
                        prev = s.snapshots[i-1].Path()
 
881
                }
 
882
 
 
883
                if err := s.send(conn, snap.Path(), prev); err != nil {
 
884
                        return err
 
885
                }
 
886
        }
 
887
 
 
888
        /* We can't send running fses, so let's snapshot the fs and send
 
889
         * the snapshot.
 
890
         */
 
891
        tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
 
892
        err := os.MkdirAll(tmpPath, 0700)
 
893
        if err != nil {
 
894
                return err
 
895
        }
 
896
 
 
897
        s.runningSnapName = fmt.Sprintf("%s/.root", tmpPath)
 
898
        if err := s.btrfs.subvolSnapshot(s.container.Path(), s.runningSnapName, true); err != nil {
 
899
                return err
 
900
        }
 
901
 
 
902
        btrfsParent := ""
 
903
        if len(s.btrfsSnapshotNames) > 0 {
 
904
                btrfsParent = s.btrfsSnapshotNames[len(s.btrfsSnapshotNames)-1]
 
905
        }
 
906
 
 
907
        return s.send(conn, s.runningSnapName, btrfsParent)
 
908
}
 
909
 
 
910
func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {
 
911
        tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
 
912
        err := os.MkdirAll(tmpPath, 0700)
 
913
        if err != nil {
 
914
                return err
 
915
        }
 
916
 
 
917
        s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
 
918
        if err := s.btrfs.subvolSnapshot(s.container.Path(), s.stoppedSnapName, true); err != nil {
 
919
                return err
 
920
        }
 
921
 
 
922
        return s.send(conn, s.stoppedSnapName, s.runningSnapName)
 
923
}
 
924
 
 
925
func (s *btrfsMigrationSourceDriver) Cleanup() {
 
926
        if s.stoppedSnapName != "" {
 
927
                s.btrfs.subvolDelete(s.stoppedSnapName)
 
928
        }
 
929
 
 
930
        if s.runningSnapName != "" {
 
931
                s.btrfs.subvolDelete(s.runningSnapName)
 
932
        }
 
933
}
 
934
 
 
935
func (s *storageBtrfs) MigrationType() MigrationFSType {
 
936
        if runningInUserns {
 
937
                return MigrationFSType_RSYNC
 
938
        } else {
 
939
                return MigrationFSType_BTRFS
 
940
        }
 
941
}
 
942
 
 
943
func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDriver, error) {
 
944
        if runningInUserns {
 
945
                return rsyncMigrationSource(c)
 
946
        }
 
947
 
 
948
        /* List all the snapshots in order of reverse creation. The idea here
 
949
         * is that we send the oldest to newest snapshot, hopefully saving on
 
950
         * xfer costs. Then, after all that, we send the container itself.
 
951
         */
 
952
        snapshots, err := c.Snapshots()
 
953
        if err != nil {
 
954
                return nil, err
 
955
        }
 
956
 
 
957
        driver := &btrfsMigrationSourceDriver{
 
958
                container:          c,
 
959
                snapshots:          snapshots,
 
960
                btrfsSnapshotNames: []string{},
 
961
                btrfs:              s,
 
962
        }
 
963
 
 
964
        for _, snap := range snapshots {
 
965
                btrfsPath := snap.Path()
 
966
                driver.btrfsSnapshotNames = append(driver.btrfsSnapshotNames, btrfsPath)
 
967
        }
 
968
 
 
969
        return driver, nil
 
970
}
 
971
 
 
972
func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
 
973
        if runningInUserns {
 
974
                return rsyncMigrationSink(live, container, snapshots, conn)
 
975
        }
 
976
 
 
977
        cName := container.Name()
 
978
 
 
979
        snapshotsPath := shared.VarPath(fmt.Sprintf("snapshots/%s", cName))
 
980
        if !shared.PathExists(snapshotsPath) {
 
981
                err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", cName)), 0700)
 
982
                if err != nil {
 
983
                        return err
 
984
                }
 
985
        }
 
986
 
 
987
        btrfsRecv := func(btrfsPath string, targetPath string, isSnapshot bool) error {
 
988
                args := []string{"receive", "-e", btrfsPath}
 
989
                cmd := exec.Command("btrfs", args...)
 
990
 
 
991
                // Remove the existing pre-created subvolume
 
992
                err := s.subvolsDelete(targetPath)
 
993
                if err != nil {
 
994
                        return err
 
995
                }
 
996
 
 
997
                stdin, err := cmd.StdinPipe()
 
998
                if err != nil {
 
999
                        return err
 
1000
                }
 
1001
 
 
1002
                stderr, err := cmd.StderrPipe()
 
1003
                if err != nil {
 
1004
                        return err
 
1005
                }
 
1006
 
 
1007
                if err := cmd.Start(); err != nil {
 
1008
                        return err
 
1009
                }
 
1010
 
 
1011
                <-shared.WebsocketRecvStream(stdin, conn)
 
1012
 
 
1013
                output, err := ioutil.ReadAll(stderr)
 
1014
                if err != nil {
 
1015
                        shared.Debugf("problem reading btrfs receive stderr %s", err)
 
1016
                }
 
1017
 
 
1018
                err = cmd.Wait()
 
1019
                if err != nil {
 
1020
                        shared.Log.Error("problem with btrfs receive", log.Ctx{"output": string(output)})
 
1021
                        return err
 
1022
                }
 
1023
 
 
1024
                if !isSnapshot {
 
1025
                        cPath := containerPath(fmt.Sprintf("%s/.root", cName), true)
 
1026
 
 
1027
                        err := s.subvolSnapshot(cPath, targetPath, false)
 
1028
                        if err != nil {
 
1029
                                shared.Log.Error("problem with btrfs snapshot", log.Ctx{"err": err})
 
1030
                                return err
 
1031
                        }
 
1032
 
 
1033
                        err = s.subvolsDelete(cPath)
 
1034
                        if err != nil {
 
1035
                                shared.Log.Error("problem with btrfs delete", log.Ctx{"err": err})
 
1036
                                return err
 
1037
                        }
 
1038
                }
 
1039
 
 
1040
                return nil
 
1041
        }
 
1042
 
 
1043
        for _, snap := range snapshots {
 
1044
                if err := btrfsRecv(containerPath(cName, true), snap.Path(), true); err != nil {
 
1045
                        return err
 
1046
                }
 
1047
        }
 
1048
 
 
1049
        /* finally, do the real container */
 
1050
        if err := btrfsRecv(containerPath(cName, true), container.Path(), false); err != nil {
 
1051
                return err
 
1052
        }
 
1053
 
 
1054
        if live {
 
1055
                if err := btrfsRecv(containerPath(cName, true), container.Path(), false); err != nil {
 
1056
                        return err
 
1057
                }
 
1058
        }
 
1059
 
 
1060
        // Cleanup
 
1061
        if ok, _ := shared.PathIsEmpty(snapshotsPath); ok {
 
1062
                err := os.Remove(snapshotsPath)
 
1063
                if err != nil {
 
1064
                        return err
 
1065
                }
 
1066
        }
 
1067
 
 
1068
        return nil
 
1069
}