github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/graphdriver/zfs/zfs.go (about) 1 //go:build linux || freebsd 2 // +build linux freebsd 3 4 package zfs // import "github.com/docker/docker/daemon/graphdriver/zfs" 5 6 import ( 7 "fmt" 8 "os" 9 "os/exec" 10 "path" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/docker/docker/daemon/graphdriver" 17 "github.com/docker/docker/pkg/containerfs" 18 "github.com/docker/docker/pkg/idtools" 19 "github.com/docker/docker/pkg/parsers" 20 zfs "github.com/mistifyio/go-zfs" 21 "github.com/moby/locker" 22 "github.com/moby/sys/mount" 23 "github.com/moby/sys/mountinfo" 24 "github.com/opencontainers/selinux/go-selinux/label" 25 "github.com/pkg/errors" 26 "github.com/sirupsen/logrus" 27 "golang.org/x/sys/unix" 28 ) 29 30 type zfsOptions struct { 31 fsName string 32 mountPath string 33 } 34 35 func init() { 36 graphdriver.Register("zfs", Init) 37 } 38 39 // Logger returns a zfs logger implementation. 40 type Logger struct{} 41 42 // Log wraps log message from ZFS driver with a prefix '[zfs]'. 43 func (*Logger) Log(cmd []string) { 44 logrus.WithField("storage-driver", "zfs").Debugf("[zfs] %s", strings.Join(cmd, " ")) 45 } 46 47 // Init returns a new ZFS driver. 48 // It takes base mount path and an array of options which are represented as key value pairs. 49 // Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options. 50 func Init(base string, opt []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) { 51 var err error 52 53 logger := logrus.WithField("storage-driver", "zfs") 54 55 if _, err := exec.LookPath("zfs"); err != nil { 56 logger.Debugf("zfs command is not available: %v", err) 57 return nil, graphdriver.ErrPrerequisites 58 } 59 60 file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 0600) 61 if err != nil { 62 logger.Debugf("cannot open /dev/zfs: %v", err) 63 return nil, graphdriver.ErrPrerequisites 64 } 65 defer file.Close() 66 67 options, err := parseOptions(opt) 68 if err != nil { 69 return nil, err 70 } 71 options.mountPath = base 72 73 rootdir := path.Dir(base) 74 75 if options.fsName == "" { 76 err = checkRootdirFs(rootdir) 77 if err != nil { 78 return nil, err 79 } 80 } 81 82 if options.fsName == "" { 83 options.fsName, err = lookupZfsDataset(rootdir) 84 if err != nil { 85 return nil, err 86 } 87 } 88 89 zfs.SetLogger(new(Logger)) 90 91 filesystems, err := zfs.Filesystems(options.fsName) 92 if err != nil { 93 return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err) 94 } 95 96 filesystemsCache := make(map[string]bool, len(filesystems)) 97 var rootDataset *zfs.Dataset 98 for _, fs := range filesystems { 99 if fs.Name == options.fsName { 100 rootDataset = fs 101 } 102 filesystemsCache[fs.Name] = true 103 } 104 105 if rootDataset == nil { 106 return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName) 107 } 108 109 dirID := idtools.Identity{ 110 UID: idtools.CurrentIdentity().UID, 111 GID: idMap.RootPair().GID, 112 } 113 if err := idtools.MkdirAllAndChown(base, 0710, dirID); err != nil { 114 return nil, fmt.Errorf("Failed to create '%s': %v", base, err) 115 } 116 117 d := &Driver{ 118 dataset: rootDataset, 119 options: options, 120 filesystemsCache: filesystemsCache, 121 idMap: idMap, 122 ctr: graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()), 123 locker: locker.New(), 124 } 125 return graphdriver.NewNaiveDiffDriver(d, idMap), nil 126 } 127 128 func parseOptions(opt []string) (zfsOptions, error) { 129 var options zfsOptions 130 options.fsName = "" 131 for _, option := range opt { 132 key, val, err := parsers.ParseKeyValueOpt(option) 133 if err != nil { 134 return options, err 135 } 136 key = strings.ToLower(key) 137 switch key { 138 case "zfs.fsname": 139 options.fsName = val 140 default: 141 return options, fmt.Errorf("Unknown option %s", key) 142 } 143 } 144 return options, nil 145 } 146 147 func lookupZfsDataset(rootdir string) (string, error) { 148 var stat unix.Stat_t 149 if err := unix.Stat(rootdir, &stat); err != nil { 150 return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err) 151 } 152 wantedDev := stat.Dev 153 154 mounts, err := mountinfo.GetMounts(nil) 155 if err != nil { 156 return "", err 157 } 158 for _, m := range mounts { 159 if err := unix.Stat(m.Mountpoint, &stat); err != nil { 160 logrus.WithField("storage-driver", "zfs").Debugf("failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err) 161 continue // may fail on fuse file systems 162 } 163 164 if stat.Dev == wantedDev && m.FSType == "zfs" { 165 return m.Source, nil 166 } 167 } 168 169 return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir) 170 } 171 172 // Driver holds information about the driver, such as zfs dataset, options and cache. 173 type Driver struct { 174 dataset *zfs.Dataset 175 options zfsOptions 176 sync.Mutex // protects filesystem cache against concurrent access 177 filesystemsCache map[string]bool 178 idMap idtools.IdentityMapping 179 ctr *graphdriver.RefCounter 180 locker *locker.Locker 181 } 182 183 func (d *Driver) String() string { 184 return "zfs" 185 } 186 187 // Cleanup is called on daemon shutdown, it is a no-op for ZFS. 188 // TODO(@cpuguy83): Walk layer tree and check mounts? 189 func (d *Driver) Cleanup() error { 190 return nil 191 } 192 193 // Status returns information about the ZFS filesystem. It returns a two dimensional array of information 194 // such as pool name, dataset name, disk usage, parent quota and compression used. 195 // Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent', 196 // 'Space Available', 'Parent Quota' and 'Compression'. 197 func (d *Driver) Status() [][2]string { 198 parts := strings.Split(d.dataset.Name, "/") 199 pool, err := zfs.GetZpool(parts[0]) 200 201 var poolName, poolHealth string 202 if err == nil { 203 poolName = pool.Name 204 poolHealth = pool.Health 205 } else { 206 poolName = fmt.Sprintf("error while getting pool information %v", err) 207 poolHealth = "not available" 208 } 209 210 quota := "no" 211 if d.dataset.Quota != 0 { 212 quota = strconv.FormatUint(d.dataset.Quota, 10) 213 } 214 215 return [][2]string{ 216 {"Zpool", poolName}, 217 {"Zpool Health", poolHealth}, 218 {"Parent Dataset", d.dataset.Name}, 219 {"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)}, 220 {"Space Available", strconv.FormatUint(d.dataset.Avail, 10)}, 221 {"Parent Quota", quota}, 222 {"Compression", d.dataset.Compression}, 223 } 224 } 225 226 // GetMetadata returns image/container metadata related to graph driver 227 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 228 return map[string]string{ 229 "Mountpoint": d.mountPath(id), 230 "Dataset": d.zfsPath(id), 231 }, nil 232 } 233 234 func (d *Driver) cloneFilesystem(name, parentName string) error { 235 snapshotName := strconv.Itoa(time.Now().Nanosecond()) 236 parentDataset := zfs.Dataset{Name: parentName} 237 snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false) 238 if err != nil { 239 return err 240 } 241 242 _, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"}) 243 if err == nil { 244 d.Lock() 245 d.filesystemsCache[name] = true 246 d.Unlock() 247 } 248 249 if err != nil { 250 snapshot.Destroy(zfs.DestroyDeferDeletion) 251 return err 252 } 253 return snapshot.Destroy(zfs.DestroyDeferDeletion) 254 } 255 256 func (d *Driver) zfsPath(id string) string { 257 return d.options.fsName + "/" + id 258 } 259 260 func (d *Driver) mountPath(id string) string { 261 return path.Join(d.options.mountPath, "graph", getMountpoint(id)) 262 } 263 264 // CreateReadWrite creates a layer that is writable for use as a container 265 // file system. 266 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { 267 return d.Create(id, parent, opts) 268 } 269 270 // Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent. 271 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { 272 var storageOpt map[string]string 273 if opts != nil { 274 storageOpt = opts.StorageOpt 275 } 276 277 err := d.create(id, parent, storageOpt) 278 if err == nil { 279 return nil 280 } 281 if zfsError, ok := err.(*zfs.Error); ok { 282 if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") { 283 return err 284 } 285 // aborted build -> cleanup 286 } else { 287 return err 288 } 289 290 dataset := zfs.Dataset{Name: d.zfsPath(id)} 291 if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil { 292 return err 293 } 294 295 // retry 296 return d.create(id, parent, storageOpt) 297 } 298 299 func (d *Driver) create(id, parent string, storageOpt map[string]string) error { 300 name := d.zfsPath(id) 301 quota, err := parseStorageOpt(storageOpt) 302 if err != nil { 303 return err 304 } 305 if parent == "" { 306 mountoptions := map[string]string{"mountpoint": "legacy"} 307 fs, err := zfs.CreateFilesystem(name, mountoptions) 308 if err == nil { 309 err = setQuota(name, quota) 310 if err == nil { 311 d.Lock() 312 d.filesystemsCache[fs.Name] = true 313 d.Unlock() 314 } 315 } 316 return err 317 } 318 err = d.cloneFilesystem(name, d.zfsPath(parent)) 319 if err == nil { 320 err = setQuota(name, quota) 321 } 322 return err 323 } 324 325 func parseStorageOpt(storageOpt map[string]string) (string, error) { 326 // Read size to change the disk quota per container 327 for k, v := range storageOpt { 328 key := strings.ToLower(k) 329 switch key { 330 case "size": 331 return v, nil 332 default: 333 return "0", fmt.Errorf("Unknown option %s", key) 334 } 335 } 336 return "0", nil 337 } 338 339 func setQuota(name string, quota string) error { 340 if quota == "0" { 341 return nil 342 } 343 fs, err := zfs.GetDataset(name) 344 if err != nil { 345 return err 346 } 347 return fs.SetProperty("quota", quota) 348 } 349 350 // Remove deletes the dataset, filesystem and the cache for the given id. 351 func (d *Driver) Remove(id string) error { 352 d.locker.Lock(id) 353 defer d.locker.Unlock(id) 354 name := d.zfsPath(id) 355 dataset := zfs.Dataset{Name: name} 356 err := dataset.Destroy(zfs.DestroyRecursive) 357 if err == nil { 358 d.Lock() 359 delete(d.filesystemsCache, name) 360 d.Unlock() 361 } 362 return err 363 } 364 365 // Get returns the mountpoint for the given id after creating the target directories if necessary. 366 func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr error) { 367 d.locker.Lock(id) 368 defer d.locker.Unlock(id) 369 mountpoint := d.mountPath(id) 370 if count := d.ctr.Increment(mountpoint); count > 1 { 371 return containerfs.NewLocalContainerFS(mountpoint), nil 372 } 373 defer func() { 374 if retErr != nil { 375 if c := d.ctr.Decrement(mountpoint); c <= 0 { 376 if mntErr := unix.Unmount(mountpoint, 0); mntErr != nil { 377 logrus.WithField("storage-driver", "zfs").Errorf("Error unmounting %v: %v", mountpoint, mntErr) 378 } 379 if rmErr := unix.Rmdir(mountpoint); rmErr != nil && !os.IsNotExist(rmErr) { 380 logrus.WithField("storage-driver", "zfs").Debugf("Failed to remove %s: %v", id, rmErr) 381 } 382 } 383 } 384 }() 385 386 filesystem := d.zfsPath(id) 387 options := label.FormatMountLabel("", mountLabel) 388 logrus.WithField("storage-driver", "zfs").Debugf(`mount("%s", "%s", "%s")`, filesystem, mountpoint, options) 389 390 root := d.idMap.RootPair() 391 // Create the target directories if they don't exist 392 if err := idtools.MkdirAllAndChown(mountpoint, 0755, root); err != nil { 393 return nil, err 394 } 395 396 if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil { 397 return nil, errors.Wrap(err, "error creating zfs mount") 398 } 399 400 // this could be our first mount after creation of the filesystem, and the root dir may still have root 401 // permissions instead of the remapped root uid:gid (if user namespaces are enabled): 402 if err := root.Chown(mountpoint); err != nil { 403 return nil, fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err) 404 } 405 406 return containerfs.NewLocalContainerFS(mountpoint), nil 407 } 408 409 // Put removes the existing mountpoint for the given id if it exists. 410 func (d *Driver) Put(id string) error { 411 d.locker.Lock(id) 412 defer d.locker.Unlock(id) 413 mountpoint := d.mountPath(id) 414 if count := d.ctr.Decrement(mountpoint); count > 0 { 415 return nil 416 } 417 418 logger := logrus.WithField("storage-driver", "zfs") 419 420 logger.Debugf(`unmount("%s")`, mountpoint) 421 422 if err := unix.Unmount(mountpoint, unix.MNT_DETACH); err != nil { 423 logger.Warnf("Failed to unmount %s mount %s: %v", id, mountpoint, err) 424 } 425 if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) { 426 logger.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err) 427 } 428 429 return nil 430 } 431 432 // Exists checks to see if the cache entry exists for the given id. 433 func (d *Driver) Exists(id string) bool { 434 d.Lock() 435 defer d.Unlock() 436 return d.filesystemsCache[d.zfsPath(id)] 437 }