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