yast/yast-storage-ng

View on GitHub
src/lib/y2storage/btrfs_subvolume.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2017-2020] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2storage/storage_class_wrapper"
require "y2storage/btrfs_qgroup"
require "y2storage/mountable"

module Y2Storage
  # A subvolume in a Btrfs filesystem
  #
  # This is a wrapper for Storage::BtrfsSubvolume
  class BtrfsSubvolume < Mountable
    wrap_class Storage::BtrfsSubvolume

    # @!method btrfs
    #   @return [Filesystems::BlkFilesystem]
    storage_forward :btrfs, as: "Filesystems::BlkFilesystem"
    alias_method :blk_filesystem, :btrfs
    alias_method :filesystem, :btrfs

    # @!method id
    #   @return [Integer]
    storage_forward :id

    # @!method top_level?
    #   @return [Boolean] whether this is the top-level subvolume
    storage_forward :top_level?

    # @!method top_level_btrfs_subvolume
    #   @return [BtrfsSubvolume] top-level subvolume
    storage_forward :top_level_btrfs_subvolume, as: "BtrfsSubvolume"

    # @!method path
    #   @return [String] path of the subvolume
    storage_forward :path

    # @!method nocow?
    #   @return [Boolean] whether No-Copy-On-Write is enabled
    storage_forward :nocow?

    # @!method nocow=(value)
    #   @see #nocow?
    #   @param value [Boolean]
    storage_forward :nocow=

    # @!method default_btrfs_subvolume?
    #   @return [Boolean] whether this is the default subvolume
    storage_forward :default_btrfs_subvolume?

    # @!method create_btrfs_subvolume(path)
    #   @param path [String] path of the new subvolume
    #   @return [BtrfsSubvolume]
    storage_forward :create_btrfs_subvolume, as: "BtrfsSubvolume"

    # @!method btrfs_qgroup
    #   Level 0 qgroup associated to this subvolume, if any
    #
    #   @return [BtrfsQgroup, nil]
    storage_forward :btrfs_qgroup, as: "BtrfsQgroup", check_with: :has_btrfs_qgroup

    # @!method create_btrfs_qgroup
    #   Creates the corresponding level 0 qgroup for the subvolume
    #
    #   Quota support must be enabled for the filesystem (see {Filesystems::Btrfs#quota?}).
    #   If that's the case, usually the qgroup already exists unless it was removed by
    #   the user.
    #
    #   @raise [Storage::Exception] if quota support is not enabled
    storage_forward :create_btrfs_qgroup, as: "BtrfsQgroup"

    # Sets this subvolume as the default one
    def set_default_btrfs_subvolume
      # The original libstorage method is wrongly renamed to
      # :default_btrfs_subvolume= by SWIG, because it's named like a setter
      # although it is not.
      to_storage_value.public_send(:default_btrfs_subvolume=)
    end

    # Assigns the default mount point for this subvolume
    #
    # Note that the current mount point is deleted if the subvolume does not require a default mount
    # point, see {#require_default_mount_point?}.
    def set_default_mount_point
      if !require_default_mount_point?
        remove_mount_point if mount_point
        return
      end

      default_mount_path = filesystem.btrfs_subvolume_mount_point(path)

      return if mount_path == default_mount_path

      remove_mount_point if mount_point

      create_mount_point(default_mount_path)
    end

    # Create a mount point with the same mount_by as the parent Btrfs.
    #
    # @param path [String]
    # @return [MountPoint]
    def create_mount_point(path)
      super
      copy_mount_by_from_filesystem
      mount_point
    end

    # Copy the parent filesystem's mount_by from the parent filesystem.
    #
    # @return [Filesystems::MountByType, nil]
    def copy_mount_by_from_filesystem
      return nil if mount_point.nil? || filesystem.mount_point.nil?

      mount_point.manual_mount_by = filesystem.mount_point.manual_mount_by?
      mount_point.assign_mount_by(filesystem.mount_point.mount_by)
    end

    # Whether the subvolume can be auto deleted, for example when a proposed
    # subvolume is shadowed
    #
    # @return [Boolean]
    def can_be_auto_deleted?
      value = userdata_value(:can_be_auto_deleted)
      value.nil? ? false : value
    end

    # @see #can_be_auto_deleted?
    def can_be_auto_deleted=(value)
      save_userdata(:can_be_auto_deleted, value)
    end

    # Size of the referenced space of the subvolume, if known
    #
    # The information is obtained from the qgroup of level 0 associated to the subvolume,
    # which implies it can only be known if quotas are enabled for the filesystem.
    # If quota support is disabled, this method returns nil.
    #
    # If the filesystem already existed during probing but got no quota support enabled at
    # that moment, this method returns zero if quota support was enabled after probing.
    #
    # @return [DiskSize, nil] nil if quota support is disabled, zero if it was enabled only
    #   after probing, the known size in any other case
    def referenced
      btrfs_qgroup&.referenced
    end

    # Size of the exclusive space of the subvolume, if known
    #
    # See {#referenced} for details about the possible returned values in several situations.
    #
    # @return [DiskSize, nil] nil if quota support is disabled, zero if it was enabled only
    #   after probing, the known size in any other case
    def exclusive
      btrfs_qgroup&.exclusive
    end

    # Limit of the referenced space for the subvolume
    #
    # @return [DiskSize] unlimited if there is no quota
    def referenced_limit
      btrfs_qgroup&.referenced_limit || DiskSize.unlimited
    end

    # Limit of the referenced space for the subvolume
    #
    # @return [DiskSize] unlimited if there is no quota
    def exclusive_limit
      btrfs_qgroup&.exclusive_limit || DiskSize.unlimited
    end

    # Setter for #{referenced_limit}
    #
    # Works only if quotas are enabled for the filesystem (see {Filesystems::Btrfs#quota?})
    #
    # @param limit [DiskSize] setting it to DiskSize.Unlimited removes the quota
    def referenced_limit=(limit)
      return unless create_missing_qgroup

      if referenced_limit && !referenced_limit.unlimited?
        save_userdata(:former_referenced_limit, referenced_limit)
      end
      btrfs_qgroup.referenced_limit = limit
    end

    # Setter for #{exclusive_limit}
    #
    # Works only if quotas are enabled for the filesystem (see {Filesystems::Btrfs#quota?})
    #
    # @param limit [DiskSize] setting it to DiskSize.Unlimited removes the quota
    def exclusive_limit=(limit)
      return unless create_missing_qgroup

      btrfs_qgroup.exclusive_limit = limit
    end

    # Previous significant (ie. not unlimited) value of {#referenced_limit}
    #
    # Used by the Partitioner to restore the value of the corresponding widget if the
    # user re-enables the limit, which improves the sense of continuity.
    #
    # @return [DiskSize, nil] nil if the limit has never changed
    def former_referenced_limit
      userdata_value(:former_referenced_limit)
    end

    # Whether the subvolume is used as prefix (typically @)
    #
    # @return [Boolean]
    def prefix?
      path == filesystem.subvolumes_prefix
    end

    # Whether the subvolume is a snapshot
    #
    # @return [Boolean]
    def snapshot?
      path.start_with?(filesystem.snapshots_root + "/")
    end

    protected

    # Whether the subvolume requires a default mount point
    #
    # Only subvolumes for root require a default mount point. This is necessary to correctly mount the
    # subvolumes when snapshoting is used. Also note that a subvolume does not require a mount point when
    # any of its ancestors already has a mount point or when the subvolume is a snapshot.
    #
    # Example:
    #
    # top-level                       -> false
    # |-- @                           -> false
    #    |-- var                      -> true
    #    |  |-- log                   -> false
    #    |-- .snapshots               -> false
    #        |--.snapshots/1/snapshot -> false
    # |-- foo                         -> true
    #
    # @return [Boolean]
    def require_default_mount_point?
      return false unless filesystem.root?
      return false if top_level? || default_btrfs_subvolume? || for_snapshots?

      parent_subvolume.top_level? || parent_subvolume.prefix?
    end

    # Whether the subvolume is used for snapshots
    #
    # @return [Boolean]
    def for_snapshots?
      path == filesystem.snapshots_root || snapshot?
    end

    # Parent subvolume
    #
    # @return [BtrfsSubvolume, nil]
    def parent_subvolume
      parents.find { |p| p.is?(:btrfs_subvolume) }
    end

    # Returns the associated level 0 qgroup, creating one if needed and possible
    #
    # @return [BtrfsQgroup, nil] nil if quotas are disabled for the filesystem, a valid
    #   qgroup (new or pre-existing) in any other case
    def create_missing_qgroup
      btrfs_qgroup || create_btrfs_qgroup
      # libstorage-ng throws an exception for #create_btrfs_qgroup if quotas are disabled
    rescue Storage::Exception
      nil
    end

    # @see Device#is?
    def types_for_is
      super << :btrfs_subvolume
    end
  end
end