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