Tizen_4.0 base
[platform/upstream/docker-engine.git] / daemon / graphdriver / btrfs / btrfs.go
1 // +build linux
2
3 package btrfs
4
5 /*
6 #include <stdlib.h>
7 #include <dirent.h>
8 #include <btrfs/ioctl.h>
9 #include <btrfs/ctree.h>
10
11 static void set_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct, const char* value) {
12     snprintf(btrfs_struct->name, BTRFS_SUBVOL_NAME_MAX, "%s", value);
13 }
14 */
15 import "C"
16
17 import (
18         "fmt"
19         "io/ioutil"
20         "math"
21         "os"
22         "path"
23         "path/filepath"
24         "strconv"
25         "strings"
26         "sync"
27         "syscall"
28         "unsafe"
29
30         "github.com/Sirupsen/logrus"
31         "github.com/docker/docker/daemon/graphdriver"
32         "github.com/docker/docker/pkg/idtools"
33         "github.com/docker/docker/pkg/mount"
34         "github.com/docker/docker/pkg/parsers"
35         "github.com/docker/docker/pkg/system"
36         "github.com/docker/go-units"
37         "github.com/opencontainers/selinux/go-selinux/label"
38 )
39
40 func init() {
41         graphdriver.Register("btrfs", Init)
42 }
43
44 type btrfsOptions struct {
45         minSpace uint64
46         size     uint64
47 }
48
49 // Init returns a new BTRFS driver.
50 // An error is returned if BTRFS is not supported.
51 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
52
53         fsMagic, err := graphdriver.GetFSMagic(home)
54         if err != nil {
55                 return nil, err
56         }
57
58         if fsMagic != graphdriver.FsMagicBtrfs {
59                 return nil, graphdriver.ErrPrerequisites
60         }
61
62         rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
63         if err != nil {
64                 return nil, err
65         }
66         if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil {
67                 return nil, err
68         }
69
70         if err := mount.MakePrivate(home); err != nil {
71                 return nil, err
72         }
73
74         opt, userDiskQuota, err := parseOptions(options)
75         if err != nil {
76                 return nil, err
77         }
78
79         driver := &Driver{
80                 home:    home,
81                 uidMaps: uidMaps,
82                 gidMaps: gidMaps,
83                 options: opt,
84         }
85
86         if userDiskQuota {
87                 if err := driver.subvolEnableQuota(); err != nil {
88                         return nil, err
89                 }
90         }
91
92         return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil
93 }
94
95 func parseOptions(opt []string) (btrfsOptions, bool, error) {
96         var options btrfsOptions
97         userDiskQuota := false
98         for _, option := range opt {
99                 key, val, err := parsers.ParseKeyValueOpt(option)
100                 if err != nil {
101                         return options, userDiskQuota, err
102                 }
103                 key = strings.ToLower(key)
104                 switch key {
105                 case "btrfs.min_space":
106                         minSpace, err := units.RAMInBytes(val)
107                         if err != nil {
108                                 return options, userDiskQuota, err
109                         }
110                         userDiskQuota = true
111                         options.minSpace = uint64(minSpace)
112                 default:
113                         return options, userDiskQuota, fmt.Errorf("Unknown option %s", key)
114                 }
115         }
116         return options, userDiskQuota, nil
117 }
118
119 // Driver contains information about the filesystem mounted.
120 type Driver struct {
121         //root of the file system
122         home         string
123         uidMaps      []idtools.IDMap
124         gidMaps      []idtools.IDMap
125         options      btrfsOptions
126         quotaEnabled bool
127         once         sync.Once
128 }
129
130 // String prints the name of the driver (btrfs).
131 func (d *Driver) String() string {
132         return "btrfs"
133 }
134
135 // Status returns current driver information in a two dimensional string array.
136 // Output contains "Build Version" and "Library Version" of the btrfs libraries used.
137 // Version information can be used to check compatibility with your kernel.
138 func (d *Driver) Status() [][2]string {
139         status := [][2]string{}
140         if bv := btrfsBuildVersion(); bv != "-" {
141                 status = append(status, [2]string{"Build Version", bv})
142         }
143         if lv := btrfsLibVersion(); lv != -1 {
144                 status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)})
145         }
146         return status
147 }
148
149 // GetMetadata returns empty metadata for this driver.
150 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
151         return nil, nil
152 }
153
154 // Cleanup unmounts the home directory.
155 func (d *Driver) Cleanup() error {
156         if err := d.subvolDisableQuota(); err != nil {
157                 return err
158         }
159
160         return mount.Unmount(d.home)
161 }
162
163 func free(p *C.char) {
164         C.free(unsafe.Pointer(p))
165 }
166
167 func openDir(path string) (*C.DIR, error) {
168         Cpath := C.CString(path)
169         defer free(Cpath)
170
171         dir := C.opendir(Cpath)
172         if dir == nil {
173                 return nil, fmt.Errorf("Can't open dir")
174         }
175         return dir, nil
176 }
177
178 func closeDir(dir *C.DIR) {
179         if dir != nil {
180                 C.closedir(dir)
181         }
182 }
183
184 func getDirFd(dir *C.DIR) uintptr {
185         return uintptr(C.dirfd(dir))
186 }
187
188 func subvolCreate(path, name string) error {
189         dir, err := openDir(path)
190         if err != nil {
191                 return err
192         }
193         defer closeDir(dir)
194
195         var args C.struct_btrfs_ioctl_vol_args
196         for i, c := range []byte(name) {
197                 args.name[i] = C.char(c)
198         }
199
200         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE,
201                 uintptr(unsafe.Pointer(&args)))
202         if errno != 0 {
203                 return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error())
204         }
205         return nil
206 }
207
208 func subvolSnapshot(src, dest, name string) error {
209         srcDir, err := openDir(src)
210         if err != nil {
211                 return err
212         }
213         defer closeDir(srcDir)
214
215         destDir, err := openDir(dest)
216         if err != nil {
217                 return err
218         }
219         defer closeDir(destDir)
220
221         var args C.struct_btrfs_ioctl_vol_args_v2
222         args.fd = C.__s64(getDirFd(srcDir))
223
224         var cs = C.CString(name)
225         C.set_name_btrfs_ioctl_vol_args_v2(&args, cs)
226         C.free(unsafe.Pointer(cs))
227
228         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2,
229                 uintptr(unsafe.Pointer(&args)))
230         if errno != 0 {
231                 return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error())
232         }
233         return nil
234 }
235
236 func isSubvolume(p string) (bool, error) {
237         var bufStat syscall.Stat_t
238         if err := syscall.Lstat(p, &bufStat); err != nil {
239                 return false, err
240         }
241
242         // return true if it is a btrfs subvolume
243         return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
244 }
245
246 func subvolDelete(dirpath, name string, quotaEnabled bool) error {
247         dir, err := openDir(dirpath)
248         if err != nil {
249                 return err
250         }
251         defer closeDir(dir)
252         fullPath := path.Join(dirpath, name)
253
254         var args C.struct_btrfs_ioctl_vol_args
255
256         // walk the btrfs subvolumes
257         walkSubvolumes := func(p string, f os.FileInfo, err error) error {
258                 if err != nil {
259                         if os.IsNotExist(err) && p != fullPath {
260                                 // missing most likely because the path was a subvolume that got removed in the previous iteration
261                                 // since it's gone anyway, we don't care
262                                 return nil
263                         }
264                         return fmt.Errorf("error walking subvolumes: %v", err)
265                 }
266                 // we want to check children only so skip itself
267                 // it will be removed after the filepath walk anyways
268                 if f.IsDir() && p != fullPath {
269                         sv, err := isSubvolume(p)
270                         if err != nil {
271                                 return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err)
272                         }
273                         if sv {
274                                 if err := subvolDelete(path.Dir(p), f.Name(), quotaEnabled); err != nil {
275                                         return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err)
276                                 }
277                         }
278                 }
279                 return nil
280         }
281         if err := filepath.Walk(path.Join(dirpath, name), walkSubvolumes); err != nil {
282                 return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err)
283         }
284
285         if quotaEnabled {
286                 if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
287                         var args C.struct_btrfs_ioctl_qgroup_create_args
288                         args.qgroupid = C.__u64(qgroupid)
289
290                         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
291                                 uintptr(unsafe.Pointer(&args)))
292                         if errno != 0 {
293                                 logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
294                         }
295                 } else {
296                         logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
297                 }
298         }
299
300         // all subvolumes have been removed
301         // now remove the one originally passed in
302         for i, c := range []byte(name) {
303                 args.name[i] = C.char(c)
304         }
305         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY,
306                 uintptr(unsafe.Pointer(&args)))
307         if errno != 0 {
308                 return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error())
309         }
310         return nil
311 }
312
313 func (d *Driver) updateQuotaStatus() {
314         d.once.Do(func() {
315                 if !d.quotaEnabled {
316                         // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
317                         if err := subvolQgroupStatus(d.home); err != nil {
318                                 // quota is still not enabled
319                                 return
320                         }
321                         d.quotaEnabled = true
322                 }
323         })
324 }
325
326 func (d *Driver) subvolEnableQuota() error {
327         d.updateQuotaStatus()
328
329         if d.quotaEnabled {
330                 return nil
331         }
332
333         dir, err := openDir(d.home)
334         if err != nil {
335                 return err
336         }
337         defer closeDir(dir)
338
339         var args C.struct_btrfs_ioctl_quota_ctl_args
340         args.cmd = C.BTRFS_QUOTA_CTL_ENABLE
341         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
342                 uintptr(unsafe.Pointer(&args)))
343         if errno != 0 {
344                 return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error())
345         }
346
347         d.quotaEnabled = true
348
349         return nil
350 }
351
352 func (d *Driver) subvolDisableQuota() error {
353         d.updateQuotaStatus()
354
355         if !d.quotaEnabled {
356                 return nil
357         }
358
359         dir, err := openDir(d.home)
360         if err != nil {
361                 return err
362         }
363         defer closeDir(dir)
364
365         var args C.struct_btrfs_ioctl_quota_ctl_args
366         args.cmd = C.BTRFS_QUOTA_CTL_DISABLE
367         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
368                 uintptr(unsafe.Pointer(&args)))
369         if errno != 0 {
370                 return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error())
371         }
372
373         d.quotaEnabled = false
374
375         return nil
376 }
377
378 func (d *Driver) subvolRescanQuota() error {
379         d.updateQuotaStatus()
380
381         if !d.quotaEnabled {
382                 return nil
383         }
384
385         dir, err := openDir(d.home)
386         if err != nil {
387                 return err
388         }
389         defer closeDir(dir)
390
391         var args C.struct_btrfs_ioctl_quota_rescan_args
392         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT,
393                 uintptr(unsafe.Pointer(&args)))
394         if errno != 0 {
395                 return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error())
396         }
397
398         return nil
399 }
400
401 func subvolLimitQgroup(path string, size uint64) error {
402         dir, err := openDir(path)
403         if err != nil {
404                 return err
405         }
406         defer closeDir(dir)
407
408         var args C.struct_btrfs_ioctl_qgroup_limit_args
409         args.lim.max_referenced = C.__u64(size)
410         args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER
411         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT,
412                 uintptr(unsafe.Pointer(&args)))
413         if errno != 0 {
414                 return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error())
415         }
416
417         return nil
418 }
419
420 // subvolQgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path
421 // with search key of BTRFS_QGROUP_STATUS_KEY.
422 // In case qgroup is enabled, the retuned key type will match BTRFS_QGROUP_STATUS_KEY.
423 // For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035
424 func subvolQgroupStatus(path string) error {
425         dir, err := openDir(path)
426         if err != nil {
427                 return err
428         }
429         defer closeDir(dir)
430
431         var args C.struct_btrfs_ioctl_search_args
432         args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID
433         args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY
434         args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY
435         args.key.max_objectid = C.__u64(math.MaxUint64)
436         args.key.max_offset = C.__u64(math.MaxUint64)
437         args.key.max_transid = C.__u64(math.MaxUint64)
438         args.key.nr_items = 4096
439
440         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH,
441                 uintptr(unsafe.Pointer(&args)))
442         if errno != 0 {
443                 return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error())
444         }
445         sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf))
446         if sh._type != C.BTRFS_QGROUP_STATUS_KEY {
447                 return fmt.Errorf("Invalid qgroup search header type for %s: %v", path, sh._type)
448         }
449         return nil
450 }
451
452 func subvolLookupQgroup(path string) (uint64, error) {
453         dir, err := openDir(path)
454         if err != nil {
455                 return 0, err
456         }
457         defer closeDir(dir)
458
459         var args C.struct_btrfs_ioctl_ino_lookup_args
460         args.objectid = C.BTRFS_FIRST_FREE_OBJECTID
461
462         _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP,
463                 uintptr(unsafe.Pointer(&args)))
464         if errno != 0 {
465                 return 0, fmt.Errorf("Failed to lookup qgroup for %s: %v", dir, errno.Error())
466         }
467         if args.treeid == 0 {
468                 return 0, fmt.Errorf("Invalid qgroup id for %s: 0", dir)
469         }
470
471         return uint64(args.treeid), nil
472 }
473
474 func (d *Driver) subvolumesDir() string {
475         return path.Join(d.home, "subvolumes")
476 }
477
478 func (d *Driver) subvolumesDirID(id string) string {
479         return path.Join(d.subvolumesDir(), id)
480 }
481
482 func (d *Driver) quotasDir() string {
483         return path.Join(d.home, "quotas")
484 }
485
486 func (d *Driver) quotasDirID(id string) string {
487         return path.Join(d.quotasDir(), id)
488 }
489
490 // CreateReadWrite creates a layer that is writable for use as a container
491 // file system.
492 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
493         return d.Create(id, parent, opts)
494 }
495
496 // Create the filesystem with given id.
497 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
498         quotas := path.Join(d.home, "quotas")
499         subvolumes := path.Join(d.home, "subvolumes")
500         rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
501         if err != nil {
502                 return err
503         }
504         if err := idtools.MkdirAllAs(subvolumes, 0700, rootUID, rootGID); err != nil {
505                 return err
506         }
507         if parent == "" {
508                 if err := subvolCreate(subvolumes, id); err != nil {
509                         return err
510                 }
511         } else {
512                 parentDir := d.subvolumesDirID(parent)
513                 st, err := os.Stat(parentDir)
514                 if err != nil {
515                         return err
516                 }
517                 if !st.IsDir() {
518                         return fmt.Errorf("%s: not a directory", parentDir)
519                 }
520                 if err := subvolSnapshot(parentDir, subvolumes, id); err != nil {
521                         return err
522                 }
523         }
524
525         var storageOpt map[string]string
526         if opts != nil {
527                 storageOpt = opts.StorageOpt
528         }
529
530         if _, ok := storageOpt["size"]; ok {
531                 driver := &Driver{}
532                 if err := d.parseStorageOpt(storageOpt, driver); err != nil {
533                         return err
534                 }
535
536                 if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil {
537                         return err
538                 }
539                 if err := idtools.MkdirAllAs(quotas, 0700, rootUID, rootGID); err != nil {
540                         return err
541                 }
542                 if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil {
543                         return err
544                 }
545         }
546
547         // if we have a remapped root (user namespaces enabled), change the created snapshot
548         // dir ownership to match
549         if rootUID != 0 || rootGID != 0 {
550                 if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil {
551                         return err
552                 }
553         }
554
555         mountLabel := ""
556         if opts != nil {
557                 mountLabel = opts.MountLabel
558         }
559
560         return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
561 }
562
563 // Parse btrfs storage options
564 func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
565         // Read size to change the subvolume disk quota per container
566         for key, val := range storageOpt {
567                 key := strings.ToLower(key)
568                 switch key {
569                 case "size":
570                         size, err := units.RAMInBytes(val)
571                         if err != nil {
572                                 return err
573                         }
574                         driver.options.size = uint64(size)
575                 default:
576                         return fmt.Errorf("Unknown option %s", key)
577                 }
578         }
579
580         return nil
581 }
582
583 // Set btrfs storage size
584 func (d *Driver) setStorageSize(dir string, driver *Driver) error {
585         if driver.options.size <= 0 {
586                 return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size)))
587         }
588         if d.options.minSpace > 0 && driver.options.size < d.options.minSpace {
589                 return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace)))
590         }
591
592         if err := d.subvolEnableQuota(); err != nil {
593                 return err
594         }
595
596         if err := subvolLimitQgroup(dir, driver.options.size); err != nil {
597                 return err
598         }
599
600         return nil
601 }
602
603 // Remove the filesystem with given id.
604 func (d *Driver) Remove(id string) error {
605         dir := d.subvolumesDirID(id)
606         if _, err := os.Stat(dir); err != nil {
607                 return err
608         }
609         quotasDir := d.quotasDirID(id)
610         if _, err := os.Stat(quotasDir); err == nil {
611                 if err := os.Remove(quotasDir); err != nil {
612                         return err
613                 }
614         } else if !os.IsNotExist(err) {
615                 return err
616         }
617
618         // Call updateQuotaStatus() to invoke status update
619         d.updateQuotaStatus()
620
621         if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil {
622                 return err
623         }
624         if err := system.EnsureRemoveAll(dir); err != nil {
625                 return err
626         }
627         if err := d.subvolRescanQuota(); err != nil {
628                 return err
629         }
630         return nil
631 }
632
633 // Get the requested filesystem id.
634 func (d *Driver) Get(id, mountLabel string) (string, error) {
635         dir := d.subvolumesDirID(id)
636         st, err := os.Stat(dir)
637         if err != nil {
638                 return "", err
639         }
640
641         if !st.IsDir() {
642                 return "", fmt.Errorf("%s: not a directory", dir)
643         }
644
645         if quota, err := ioutil.ReadFile(d.quotasDirID(id)); err == nil {
646                 if size, err := strconv.ParseUint(string(quota), 10, 64); err == nil && size >= d.options.minSpace {
647                         if err := d.subvolEnableQuota(); err != nil {
648                                 return "", err
649                         }
650                         if err := subvolLimitQgroup(dir, size); err != nil {
651                                 return "", err
652                         }
653                 }
654         }
655
656         return dir, nil
657 }
658
659 // Put is not implemented for BTRFS as there is no cleanup required for the id.
660 func (d *Driver) Put(id string) error {
661         // Get() creates no runtime resources (like e.g. mounts)
662         // so this doesn't need to do anything.
663         return nil
664 }
665
666 // Exists checks if the id exists in the filesystem.
667 func (d *Driver) Exists(id string) bool {
668         dir := d.subvolumesDirID(id)
669         _, err := os.Stat(dir)
670         return err == nil
671 }