yast/yast-storage-ng

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

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2015] 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 "yast"
require "y2storage/boot_requirements_strategies"
require "y2storage/storage_manager"

module Y2Storage
  #
  # Class that can check requirements for the different kinds of boot
  # partition: /boot, EFI-boot, PReP.
  #
  # See also
  # https://github.com/yast/yast-bootloader/blob/master/SUPPORTED_SCENARIOS.md
  #
  class BootRequirementsChecker
    include Yast::Logger

    class Error < RuntimeError
    end

    # Constructor
    #
    # @see #devicegraph
    # @see #planned_devices
    # @see #boot_disk_name
    def initialize(devicegraph, planned_devices: [], boot_disk_name: nil)
      @devicegraph = devicegraph
      @planned_devices = planned_devices
      @boot_disk_name = boot_disk_name
    end

    # Partitions needed in order to be able to boot the system
    #
    # So far, this method is used by {GuidedProposal} and by {AutoinstProposal},
    # since both need to know which partitions they should create.
    #
    # At the moment of writing, this is not used directly by {SetupChecker}
    # (the mechanism used by the Partitioner to find and report wrong setups).
    # See {#errors} and {#warnings} for that.
    #
    # @raise [BootRequirementsChecker::Error] if adding partitions is not
    #     enough to make the system bootable
    #
    # @param target [Symbol] :desired means the sizes of the partitions should
    #   be the ideal ones, :min for generating the smallest functional partitions
    #
    # @return [Array<Planned::Partition>]
    def needed_partitions(target = :desired)
      strategy.needed_partitions(target)
    rescue BootRequirementsStrategies::Error => e
      raise Error, e.message
    end

    # Whether the current setup contains all necessary devices for booting
    #
    # @return [Boolean]
    def valid?
      errors.empty? && warnings.empty?
    end

    # All boot warnings detected in the setup, for example, when size of a /boot/efi partition
    # is out of borders.
    #
    # So far, this is mainly used by {SetupError} (which is the mechanism used
    # by the partitioner). The proposals rely on {#needed_partitions} instead.
    #
    # @return [Array<SetupError>]
    def warnings
      strategy.warnings
    end

    # All fatal boot errors detected in the setup, for example, when a /boot/efi partition
    # is missing in a UEFI system
    #
    # So far, this is mainly used by {SetupError} (which is the mechanism used
    # by the partitioner). The proposals rely on {#needed_partitions} instead.
    #
    # @return [Array<SetupError>]
    def errors
      strategy.errors
    end

    protected

    # @return [Devicegraph] starting situation.
    attr_reader :devicegraph

    # @return [Array<Planned::Device>] devices that are already planned to be
    #   added to the starting devicegraph.
    attr_reader :planned_devices

    # @return [String, nil] device name of the disk that the system will try to
    #   boot first. Only useful in some scenarios like legacy boot.
    #   See {BootRequirementsStrategies::Analyzer#boot_disk}.
    attr_reader :boot_disk_name

    def arch
      @arch ||= StorageManager.instance.arch
    end

    def strategy
      @strategy ||= strategy_class.new(devicegraph, planned_devices, boot_disk_name)
    end

    # @see #strategy
    #
    # @return [BootRequirementsStrategies::Base]
    def strategy_class
      if nfs_root?
        BootRequirementsStrategies::NfsRoot
      elsif raspberry_pi?
        BootRequirementsStrategies::Raspi
      else
        arch_strategy_class
      end
    end

    # @see #strategy
    #
    # @return [BootRequirementsStrategies::Base]
    def arch_strategy_class
      if arch.efiboot?
        BootRequirementsStrategies::UEFI
      elsif arch.s390?
        BootRequirementsStrategies::ZIPL
      elsif arch.ppc?
        BootRequirementsStrategies::PReP
      else
        # Fallback to Legacy as default
        BootRequirementsStrategies::Legacy
      end
    end

    # Whether the root filesystem is NFS
    #
    # @return [Boolean]
    def nfs_root?
      planned_nfs_root? || exist_nfs_root?
    end

    # Whether there is a planned NFS root
    #
    # @return [Boolean]
    def planned_nfs_root?
      planned_devices.find { |d| d.is_a?(Planned::Nfs) && d.mount_point == "/" }
    end

    # Whether a NFS root already exists
    #
    # @return [Boolean]
    def exist_nfs_root?
      devicegraph.nfs_mounts.any? { |i| i.mount_point&.root? }
    end

    # @see #raspberry_pi?
    VENDOR_MODEL_PATH = "/proc/device-tree/model"
    private_constant :VENDOR_MODEL_PATH

    # Whether this is a Raspberry Pi. See fate#323484
    #
    # @return [Boolean]
    def raspberry_pi?
      return false unless File.exist?(VENDOR_MODEL_PATH)

      File.read(VENDOR_MODEL_PATH).match?(/Raspberry Pi/i)
    end
  end
end